Merge branch 'geoapi-3.1'
diff --git a/.asf.yaml b/.asf.yaml
new file mode 100644
index 0000000..9e65d00
--- /dev/null
+++ b/.asf.yaml
@@ -0,0 +1,15 @@
+#
+# Configuration file for Apache SIS project.
+#
+# Documentation:  https://s.apache.org/asfyaml
+# Active setting: https://gitbox.apache.org/schemes.cgi?sis-site
+#
+notifications:
+  commits:              commits@sis.apache.org
+  issues:               dev@sis.apache.org
+  issues_status:        issues@sis.apache.org
+  issues_comment:       issues@sis.apache.org
+  pullrequests:         dev@sis.apache.org
+  pullrequests_status:  issues@sis.apache.org
+  pullrequests_comment: issues@sis.apache.org
+  jira_options:         worklog
diff --git a/application/pom.xml b/application/pom.xml
index 0db0ec5..ae0cf3e 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/application/sis-console/pom.xml b/application/sis-console/pom.xml
index 6bec4bb..6ef2877 100644
--- a/application/sis-console/pom.xml
+++ b/application/sis-console/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>application</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/application/sis-console/src/main/artifact/bin/sis b/application/sis-console/src/main/artifact/bin/sis
index d0560d9..35a0e94 100755
--- a/application/sis-console/src/main/artifact/bin/sis
+++ b/application/sis-console/src/main/artifact/bin/sis
@@ -24,7 +24,7 @@
 export SIS_DATA
 
 # Execute SIS with any optional JAR that the user may put in the 'lib' directory.
-java -classpath "$BASE_DIR/lib/sis-console-1.x-SNAPSHOT.jar" \
+java -classpath "$BASE_DIR/lib/sis-console-1.3-SNAPSHOT.jar" \
      -Djava.util.logging.config.file="$BASE_DIR/conf/logging.properties" \
      -Dderby.stream.error.file="$BASE_DIR/log/derby.log" \
      org.apache.sis.console.Command $SIS_OPTS "$@"
diff --git a/application/sis-console/src/main/java/org/apache/sis/console/IdentifierCommand.java b/application/sis-console/src/main/java/org/apache/sis/console/IdentifierCommand.java
index 9171156..2d7979c 100644
--- a/application/sis-console/src/main/java/org/apache/sis/console/IdentifierCommand.java
+++ b/application/sis-console/src/main/java/org/apache/sis/console/IdentifierCommand.java
@@ -36,6 +36,10 @@
 import org.apache.sis.util.Workaround;
 import org.apache.sis.util.resources.Vocabulary;
 
+// Branch-dependent imports
+import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
+
 
 /**
  * The "identifier" sub-command.
@@ -124,11 +128,11 @@
         }
         if (metadata != null) {
             final List<Row> rows;
-            if (metadata instanceof Metadata) {
+            if (metadata instanceof DefaultMetadata) {
                 rows = new ArrayList<>();
-                final Identifier id = ((Metadata) metadata).getMetadataIdentifier();
-                if (id != null) {
-                    CharSequence desc = id.getDescription();
+                final Identifier id = ((DefaultMetadata) metadata).getMetadataIdentifier();
+                if (id instanceof DefaultIdentifier) {
+                    CharSequence desc = ((DefaultIdentifier) id).getDescription();
                     if (desc != null && !files.isEmpty()) desc = files.get(0);
                     rows.add(new Row(State.VALID, IdentifiedObjects.toString(id), desc));
                 }
diff --git a/application/sis-console/src/test/java/org/apache/sis/console/MetadataCommandTest.java b/application/sis-console/src/test/java/org/apache/sis/console/MetadataCommandTest.java
index 3b61f36..8dcd9dd 100644
--- a/application/sis-console/src/test/java/org/apache/sis/console/MetadataCommandTest.java
+++ b/application/sis-console/src/test/java/org/apache/sis/console/MetadataCommandTest.java
@@ -17,10 +17,10 @@
 package org.apache.sis.console;
 
 import java.net.URL;
-import org.opengis.test.dataset.TestData;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
@@ -42,8 +42,9 @@
      * @throws Exception if an error occurred while creating the command.
      */
     @Test
+    @Ignore("Requires GeoAPI 3.1")
     public void testNetCDF() throws Exception {
-        final URL url = TestData.NETCDF_2D_GEOGRAPHIC.location();
+        final URL url = new URL("Cube2D_geographic_packed.nc"); // TestData.NETCDF_2D_GEOGRAPHIC.location();
         final MetadataCommand test = new MetadataCommand(0, CommandRunner.TEST, url.toString());
         test.run();
         verifyNetCDF("Metadata", test.outputBuffer.toString());
@@ -66,9 +67,10 @@
      * @throws Exception if an error occurred while creating the command.
      */
     @Test
+    @Ignore("Requires GeoAPI 3.1")
     @DependsOnMethod("testNetCDF")
     public void testFormatXML() throws Exception {
-        final URL url = TestData.NETCDF_2D_GEOGRAPHIC.location();
+        final URL url = new URL("Cube2D_geographic_packed.nc") ; // TestData.NETCDF_2D_GEOGRAPHIC.location();
         final MetadataCommand test = new MetadataCommand(0, CommandRunner.TEST, url.toString(), "--format", "XML");
         test.run();
         verifyNetCDF("<?xml", test.outputBuffer.toString());
diff --git a/application/sis-javafx/pom.xml b/application/sis-javafx/pom.xml
index 80a0a50..e25336c 100644
--- a/application/sis-javafx/pom.xml
+++ b/application/sis-javafx/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>application</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/application/sis-javafx/src/main/artifact/bin/sisfx b/application/sis-javafx/src/main/artifact/bin/sisfx
index 90b93e9..05e88ca 100755
--- a/application/sis-javafx/src/main/artifact/bin/sisfx
+++ b/application/sis-javafx/src/main/artifact/bin/sisfx
@@ -38,7 +38,7 @@
 java -splash:"$BASE_DIR/lib/logo.jpg" \
      --add-modules javafx.graphics,javafx.controls,javafx.web \
      --module-path "$PATH_TO_FX" \
-     --class-path "$BASE_DIR/lib/sis-javafx-1.x-SNAPSHOT.jar" \
+     --class-path "$BASE_DIR/lib/sis-javafx-1.3-SNAPSHOT.jar" \
      -Djava.util.logging.config.class="org.apache.sis.internal.setup.LoggingConfiguration" \
      -Djava.util.logging.config.file="$BASE_DIR/conf/logging.properties" \
      -Dderby.stream.error.file="$BASE_DIR/log/derby.log" \
diff --git a/application/sis-javafx/src/main/artifact/bin/sisfx.bat b/application/sis-javafx/src/main/artifact/bin/sisfx.bat
index aa85eba..4eda222 100644
--- a/application/sis-javafx/src/main/artifact/bin/sisfx.bat
+++ b/application/sis-javafx/src/main/artifact/bin/sisfx.bat
@@ -29,7 +29,7 @@
 java -splash:"%BASE_DIR%\lib\logo.jpg"^
  --add-modules javafx.graphics,javafx.controls,javafx.web^
  --module-path "%PATH_TO_FX%"^
- --class-path "%BASE_DIR%\lib\sis-javafx-1.x-SNAPSHOT.jar"^
+ --class-path "%BASE_DIR%\lib\sis-javafx-1.3-SNAPSHOT.jar"^
  -Djava.util.logging.config.class=org.apache.sis.internal.setup.LoggingConfiguration^
  -Djava.util.logging.config.file="%BASE_DIR%\conf\logging.properties"^
  -Dderby.stream.error.file="%BASE_DIR%\log\derby.log"^
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
index daf3850..f3c486e 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -145,7 +145,7 @@
      * when data are ready.
      *
      * <p>Current implementation is restricted to {@link GridCoverage} instances, but a future
-     * implementation may generalize to {@link org.opengis.coverage.Coverage} instances.</p>
+     * implementation may generalize to {@code org.opengis.coverage.Coverage} instances.</p>
      *
      * @see #getCoverage()
      * @see #setCoverage(GridCoverage)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
index 9823132..68e8113 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
@@ -68,7 +68,7 @@
  *
  * <h2>Limitations</h2>
  * Current implementation is restricted to {@link GridCoverage} instances, but a future
- * implementation may generalize to {@link org.opengis.coverage.Coverage} instances.
+ * implementation may generalize to {@code org.opengis.coverage.Coverage} instances.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.3
@@ -167,7 +167,7 @@
      * when data are ready.
      *
      * <p>Current implementation is restricted to {@link GridCoverage} instances, but a future
-     * implementation may generalize to {@link org.opengis.coverage.Coverage} instances.</p>
+     * implementation may generalize to {@code org.opengis.coverage.Coverage} instances.</p>
      *
      * <h4>Relationship with view properties</h4>
      * This property is "weakly bound" to {@link CoverageCanvas#coverageProperty}:
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandableList.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandableList.java
index ebce554..c6df2fd 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandableList.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandableList.java
@@ -33,13 +33,13 @@
 import javafx.scene.layout.BackgroundFill;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.internal.gui.Styles;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Feature;
+
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
  * Wraps a {@link FeatureList} with the capability to expand the multi-valued properties of
- * a selected {@link Feature}. The expansion appears as additional rows below the feature.
+ * a selected {@code Feature}. The expansion appears as additional rows below the feature.
  * This view is used only if the feature type contains at least one property type with a
  * maximum number of occurrence greater than 1.
  *
@@ -48,8 +48,8 @@
  * @since   1.1
  * @module
  */
-final class ExpandableList extends TransformationList<Feature,Feature>
-        implements Callback<TableColumn<Feature,Feature>, TableCell<Feature,Feature>>,
+final class ExpandableList extends TransformationList<AbstractFeature,AbstractFeature>
+        implements Callback<TableColumn<AbstractFeature,AbstractFeature>, TableCell<AbstractFeature,AbstractFeature>>,
                    EventHandler<MouseEvent>
 {
     /**
@@ -107,7 +107,7 @@
 
     /**
      * Specifies the names of properties that may be multi-valued. This method needs to be invoked
-     * only if the {@link FeatureType} changed. This method shall not be invoked if there is any
+     * only if the {@code FeatureType} changed. This method shall not be invoked if there is any
      * {@link #expansion} rows. Normally this list will be empty at invocation time.
      *
      * @param  columnNames  names of properties that may contain multi-values.
@@ -133,8 +133,8 @@
      *
      * @return the removed rows, or {@code null} if none.
      */
-    private List<Feature> shrink() {
-        final List<Feature> removed = (expansion == null) ? null
+    private List<AbstractFeature> shrink() {
+        final List<AbstractFeature> removed = (expansion == null) ? null
                                     : UnmodifiableArrayList.wrap(expansion, 1, expansion.length);
         expansion       = null;
         indexOfExpanded = Integer.MAX_VALUE;
@@ -149,7 +149,7 @@
     @Override
     public void clear() {
         final int removeAfter = indexOfExpanded;
-        final List<Feature> removed = shrink();
+        final List<AbstractFeature> removed = shrink();
         if (removed != null) {
             beginChange();
             nextUpdate(removeAfter);
@@ -173,7 +173,7 @@
         final IconCell cell = (IconCell) event.getSource();
         final int index = getSourceIndex(cell.getIndex());      // Must be invoked before `shrink()`.
         final int removeAfter = indexOfExpanded;
-        final List<Feature> removed = shrink();
+        final List<AbstractFeature> removed = shrink();
 //      index = getViewIndex(index);                // Not needed for current single-selection model.
         /*
          * If a new row is selected, extract now all properties. We need at least the number
@@ -215,7 +215,7 @@
     /**
      * Returns {@code true} if the given feature contains more than one row.
      */
-    private boolean isExpandable(final Feature feature) {
+    private boolean isExpandable(final AbstractFeature feature) {
         if (feature != null) {
             for (final String name : nameToIndex.keySet()) {
                 final Object value = feature.getPropertyValue(name);
@@ -247,7 +247,7 @@
      * except if the given index is for an expanded row.
      */
     @Override
-    public Feature get(int index) {
+    public AbstractFeature get(int index) {
         final int i = index - indexOfExpanded;
         if (i >= 0) {
             final int n = expansion.length;     // A NullPointerException here would be an ExpandableList bug.
@@ -260,7 +260,7 @@
     /**
      * Given an index in this expanded list, returns the index of corresponding element in the feature list.
      * All indices from {@link #indexOfExpanded} inclusive to <code>{@linkplain #indexOfExpanded} +
-     * {@linkplain #expansion}.length</code> exclusive map to the same {@link Feature} instance.
+     * {@linkplain #expansion}.length</code> exclusive map to the same {@link AbstractFeature} instance.
      *
      * @param  index  index in this expandable list.
      * @return index of the corresponding element in {@link FeatureList}.
@@ -297,8 +297,8 @@
      * {@link FeatureList} and converts source indices to indices of this expandable list.
      */
     @Override
-    protected void sourceChanged(final ListChangeListener.Change<? extends Feature> c) {
-        fireChange(new ListChangeListener.Change<Feature>(this) {
+    protected void sourceChanged(final ListChangeListener.Change<? extends AbstractFeature> c) {
+        fireChange(new ListChangeListener.Change<AbstractFeature>(this) {
             @Override public void     reset()               {c.reset();}
             @Override public boolean  next()                {return c.next();}
             @Override public boolean  wasAdded()            {return c.wasAdded();}
@@ -325,8 +325,8 @@
 
             @Override
             @SuppressWarnings("unchecked")
-            public List<Feature> getRemoved() {
-                return (List<Feature>) expandRemoved(c.getFrom(), c.getRemoved());
+            public List<AbstractFeature> getRemoved() {
+                return (List<AbstractFeature>) expandRemoved(c.getFrom(), c.getRemoved());
             }
         });
     }
@@ -347,13 +347,13 @@
      * @param  removed     the removed elements provided by the {@link FeatureList}.
      * @return the removed elements as seen by this {@code ExpandableList}.
      */
-    private List<? extends Feature> expandRemoved(final int sourceFrom, final List<? extends Feature> removed) {
+    private List<? extends AbstractFeature> expandRemoved(final int sourceFrom, final List<? extends AbstractFeature> removed) {
         if (!overlapExpanded(sourceFrom, removed.size())) {
             return removed;
         }
         final int s = indexOfExpanded;
         final int n = expansion.length;         // A NullPointerException here would be an ExpandableList bug.
-        final Feature[] features = removed.toArray(new Feature[removed.size() + (n - 1)]);
+        final AbstractFeature[] features = removed.toArray(new AbstractFeature[removed.size() + (n - 1)]);
         System.arraycopy(features,  s+1, features, s + n, features.length - (s+1));
         System.arraycopy(expansion, 0,   features, s,  n);
         return Arrays.asList(features);
@@ -367,7 +367,7 @@
      * @param  column  the column where the cell will be shown.
      */
     @Override
-    public TableCell<Feature,Feature> call(final TableColumn<Feature,Feature> column) {
+    public TableCell<AbstractFeature,AbstractFeature> call(final TableColumn<AbstractFeature,AbstractFeature> column) {
         return new IconCell();
     }
 
@@ -375,7 +375,7 @@
      * The cell which represents whether a row is expandable or expanded.
      * If visible, this is the first column in the table.
      */
-    private final class IconCell extends TableCell<Feature,Feature>  {
+    private final class IconCell extends TableCell<AbstractFeature,AbstractFeature>  {
         /**
          * Whether this cell is listening to mouse click events.
          */
@@ -394,7 +394,7 @@
          * The call will have a listener only if it has an icon.
          */
         @Override
-        protected void updateItem(final Feature value, final boolean empty) {
+        protected void updateItem(final AbstractFeature value, final boolean empty) {
             super.updateItem(value, empty);
             Background b = null;
             String  text = null;
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandedFeature.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandedFeature.java
index 32e7254..64c8a59 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandedFeature.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandedFeature.java
@@ -20,9 +20,8 @@
 import java.util.Map;
 import java.util.Collection;
 import java.util.Collections;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Property;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -37,7 +36,7 @@
  * @since   1.1
  * @module
  */
-final class ExpandedFeature implements Feature {
+final class ExpandedFeature extends AbstractFeature {
     /**
      * The array for properties having no value.
      * This is the fill value for {@link #values} array.
@@ -49,12 +48,12 @@
      * This feature may contain a mix of single-valued and multi-valued properties.
      * Values of multi-valued properties are copied in the {@link #values} array.
      */
-    private final Feature source;
+    private final AbstractFeature source;
 
     /**
      * Mapping from property names to index in the {@link #values} array.
      * <strong>Do not modify,</strong> because all {@link ExpandedFeature} instances created
-     * after a call to {@link FeatureTable#setFeatureType(FeatureType)} share the same map.
+     * after a call to {@link FeatureTable#setFeatureType(DefaultFeatureType)} share the same map.
      */
     private final Map<String,Integer> nameToIndex;
 
@@ -76,9 +75,10 @@
     /**
      * Creates a new feature wrapping the given source.
      */
-    private ExpandedFeature(final Feature source, final Map<String,Integer> nameToIndex,
+    private ExpandedFeature(final AbstractFeature source, final Map<String,Integer> nameToIndex,
                             final Object[][] values, final int index)
     {
+        super(source.getType());
         this.source      = source;
         this.nameToIndex = nameToIndex;
         this.values      = values;
@@ -95,7 +95,7 @@
      * @param  nameToIndex  mapping from property names to index in the {@code values} array.
      * @return pseudo-features for property elements at all indices, or {@code null} if none.
      */
-    static ExpandedFeature[] create(final Feature source, final Map<String,Integer> nameToIndex) {
+    static ExpandedFeature[] create(final AbstractFeature source, final Map<String,Integer> nameToIndex) {
         if (source != null) {
             final Object[][] values = new Object[nameToIndex.size()][];
             Arrays.fill(values, EMPTY);
@@ -131,7 +131,7 @@
      * Returns the source feature type verbatim.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return source.getType();
     }
 
@@ -140,7 +140,7 @@
      * This method is not used by {@link FeatureTable} so we just delegate to the source.
      */
     @Override
-    public Property getProperty(final String name) {
+    public Object getProperty(final String name) {
         return source.getProperty(name);
     }
 
@@ -149,7 +149,7 @@
      * This method is not used by {@link FeatureTable} so we just forward to the source.
      */
     @Override
-    public void setProperty(final Property property) {
+    public void setProperty(final Object property) {
         source.setProperty(property);
     }
 
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java
index 99a45768..da16811 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java
@@ -22,11 +22,11 @@
 import java.util.Spliterator;
 import javafx.application.Platform;
 import javafx.collections.ObservableListBase;
-import org.opengis.feature.Feature;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.internal.gui.BackgroundThreads;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -53,7 +53,7 @@
  * @since   1.1
  * @module
  */
-final class FeatureList extends ObservableListBase<Feature> {
+final class FeatureList extends ObservableListBase<AbstractFeature> {
     /**
      * Number of empty rows to show in the bottom of the table when we don't know how many rows still
      * need to be read. Those rows do not stay empty for long since they will become valid as soon as
@@ -69,12 +69,12 @@
     /**
      * The {@link #elements} value when this list is empty.
      */
-    private static final Feature[] EMPTY = new Feature[0];
+    private static final AbstractFeature[] EMPTY = new AbstractFeature[0];
 
     /**
      * The elements in this list, never {@code null}.
      */
-    private Feature[] elements;
+    private AbstractFeature[] elements;
 
     /**
      * Number of valid elements in {@link #elements}.
@@ -114,7 +114,7 @@
     /**
      * Returns the currently valid elements.
      */
-    private List<Feature> validElements() {
+    private List<AbstractFeature> validElements() {
         return UnmodifiableArrayList.wrap(elements, 0, validCount);
     }
 
@@ -126,7 +126,7 @@
      */
     @Override
     public void clear() {
-        final List<Feature> removed = validElements();
+        final List<AbstractFeature> removed = validElements();
         elements = EMPTY;
         estimatedSize = 0;
         validCount    = 0;
@@ -164,7 +164,7 @@
     /**
      * Invoked by {@link FeatureLoader} for replacing the current content by a new list of features.
      * The list size after this method invocation will be {@code expectedSize}, not {@code count}.
-     * The missing elements will be implicitly null until {@link #addFeatures(Feature[], int, boolean)}
+     * The missing elements will be implicitly null until {@link #addFeatures(AbstractFeature[], int, boolean)}
      * is invoked. If the expected size is unknown (i.e. its value is {@link Long#MAX_VALUE}),
      * then an arbitrary size is computed from {@code count}.
      *
@@ -176,15 +176,15 @@
      */
     @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
     final void setFeatures(long remainingCount, int characteristics,
-                           final Feature[] features, final int count, final boolean hasMore)
+                           final AbstractFeature[] features, final int count, final boolean hasMore)
     {
         assert Platform.isFxApplicationThread();
         int newValidCount = 0;
         for (int i=0; i<count; i++) {
-            final Feature f = features[i];
+            final AbstractFeature f = features[i];
             if (f != null) features[newValidCount++] = f;       // Exclude null elements.
         }
-        final List<Feature> removed = validElements();          // Want this call outside {beginChange … endChange}.
+        final List<AbstractFeature> removed = validElements();  // Want this call outside {beginChange … endChange}.
         if (remainingCount == Long.MAX_VALUE) {
             remainingCount  = count + NUM_PENDING_ROWS;         // Arbitrary additional amount.
             characteristics = 0;
@@ -212,7 +212,7 @@
      * @param  hasMore   if the stream may have more features.
      * @throws ArithmeticException if the number of elements exceeds this list capacity.
      */
-    final void addFeatures(final Feature[] features, final int count, final boolean hasMore) {
+    final void addFeatures(final AbstractFeature[] features, final int count, final boolean hasMore) {
         assert Platform.isFxApplicationThread();
         if (count > 0) {
             int newValidCount = Math.addExact(validCount, count);
@@ -222,7 +222,7 @@
             }
             newValidCount = validCount;         // Recompute `validCount + count` but excluding null elements.
             for (int i=0; i<count; i++) {
-                final Feature f = features[i];
+                final AbstractFeature f = features[i];
                 if (f != null) elements[newValidCount++] = f;
             }
             /*
@@ -230,7 +230,7 @@
              * Only if the new size exceeds the previously expected size, we send a notification about addition.
              */
             final int replaceTo = Math.min(newValidCount, estimatedSize);
-            final List<Feature> removed = Collections.nCopies(replaceTo - validCount, null);
+            final List<AbstractFeature> removed = Collections.nCopies(replaceTo - validCount, null);
             if (newValidCount >= estimatedSize) {
                 estimatedSize = newValidCount;              // Update before we send events.
                 if (hasMore) {
@@ -274,7 +274,7 @@
         if (next == null) {
             final int n = estimatedSize - validCount;
             if (n != 0) {
-                final List<Feature> removed = Collections.nCopies(n, null);
+                final List<AbstractFeature> removed = Collections.nCopies(n, null);
                 estimatedSize = validCount;
                 beginChange();
                 nextRemove(validCount, removed);
@@ -307,7 +307,7 @@
      * but has not yet been loaded, returns {@code null}.
      */
     @Override
-    public Feature get(final int index) {
+    public AbstractFeature get(final int index) {
         assert Platform.isFxApplicationThread();
         if (index < validCount) {
             return elements[index];
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureLoader.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureLoader.java
index d829057..5eb0792 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureLoader.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureLoader.java
@@ -23,12 +23,12 @@
 import java.util.concurrent.ExecutionException;
 import javafx.application.Platform;
 import javafx.concurrent.Task;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -41,7 +41,7 @@
  * @since   1.1
  * @module
  */
-final class FeatureLoader extends Task<Boolean> implements Consumer<Feature> {
+final class FeatureLoader extends Task<Boolean> implements Consumer<AbstractFeature> {
     /**
      * Maximum number of features to load in a background task.
      * If there is more features to load, we will use many tasks.
@@ -67,18 +67,18 @@
      * The stream to close after we finished to iterate over features.
      * This stream should not be used for any other purpose.
      */
-    private Stream<Feature> toClose;
+    private Stream<AbstractFeature> toClose;
 
     /**
      * If the reading process is not finished, the iterator for reading more feature instances.
      */
-    private Spliterator<Feature> iterator;
+    private Spliterator<AbstractFeature> iterator;
 
     /**
      * The features loaded by this task. This array is created in a background thread,
      * then added to {@link #table} in the JavaFX thread.
      */
-    private Feature[] loaded;
+    private AbstractFeature[] loaded;
 
     /**
      * Number of features loaded by this task.
@@ -110,7 +110,7 @@
      * defined for {@link #call()} internal purpose only.
      */
     @Override
-    public void accept(final Feature feature) {
+    public void accept(final AbstractFeature feature) {
         loaded[count++] = feature;
     }
 
@@ -141,7 +141,7 @@
          */
         final long remaining = iterator.estimateSize();
         final int stopAt = (remaining > PAGE_SIZE) ? PAGE_SIZE : 1 + (int) remaining;
-        loaded = new Feature[stopAt];
+        loaded = new AbstractFeature[stopAt];
         try {
             while (iterator.tryAdvance(this)) {
                 if (count >= stopAt) {
@@ -173,7 +173,7 @@
      */
     private void close() throws DataStoreException {
         iterator = null;
-        final Stream<Feature> c = toClose;
+        final Stream<AbstractFeature> c = toClose;
         if (c != null) try {
             toClose = null;                             // Clear now in case an exception happens below.
             c.close();
@@ -304,7 +304,7 @@
 
     /**
      * Invoked when the feature type may have been found. If the given type is non-null,
-     * then this method delegates to {@link FeatureTable#setFeatureType(FeatureType)} in
+     * then this method delegates to {@link FeatureTable#setFeatureType(DefaultFeatureType)} in
      * the JavaFX thread. This will erase the previous content and prepare new columns.
      *
      * <p>This method is invoked, directly or indirectly, only from the {@link #call()}
@@ -314,7 +314,7 @@
      * @param  type  the feature type, or {@code null}.
      * @return whether the given type was non-null.
      */
-    private boolean setType(final FeatureType type) {
+    private boolean setType(final DefaultFeatureType type) {
         if (type != null) {
             Platform.runLater(() -> table.setFeatureType(type));
             return true;
@@ -332,7 +332,7 @@
     private void setMissingType(final boolean isTypeKnown) throws DataStoreException {
         if (!isTypeKnown) {
             for (int i=0; i<count; i++) {
-                final Feature f = loaded[i];
+                final AbstractFeature f = loaded[i];
                 if (f != null && setType(f.getType())) {
                     return;
                 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
index 9e02399..b1ad7d7 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
@@ -36,11 +36,6 @@
 import javafx.collections.ObservableList;
 import javafx.geometry.Pos;
 import javafx.util.Callback;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.util.Strings;
@@ -53,6 +48,12 @@
 
 import static java.util.logging.Logger.getLogger;
 
+// Branch-dependent imports
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+
 
 /**
  * A view of {@link FeatureSet} data organized as a table. The features are specified by a call
@@ -71,7 +72,7 @@
  *   <li>The list returned by {@link #getItems()} should be considered read-only.</li>
  * </ul>
  *
- * @todo This class does not yet handle {@link FeatureAssociationRole}. We could handle them with
+ * @todo This class does not yet handle {@code FeatureAssociationRole}. We could handle them with
  *       {@link javafx.scene.control.SplitPane} with the main feature table in the upper part and
  *       the feature table of selected cell in the bottom part. Bottom part could put tables in a
  *       {@link javafx.scene.control.Accordion} since there is possibly different tables to show
@@ -85,7 +86,7 @@
  * @module
  */
 @DefaultProperty("features")
-public class FeatureTable extends TableView<Feature> {
+public class FeatureTable extends TableView<AbstractFeature> {
     /**
      * The locale to use for texts. This is usually {@link Locale#getDefault()}.
      * This value is given to {@link InternationalString#toString(Locale)} calls.
@@ -96,9 +97,9 @@
      * The type of features, or {@code null} if not yet determined.
      * This type determines the columns that will be shown.
      *
-     * @see #setFeatureType(FeatureType)
+     * @see #setFeatureType(DefaultFeatureType)
      */
-    private FeatureType featureType;
+    private DefaultFeatureType featureType;
 
     /**
      * The data shown in this table. Note that setting this property to a non-null value
@@ -112,7 +113,7 @@
 
     /**
      * Whether the {@link #getItems()} list may be shared by another {@link FeatureTable} instance.
-     * In such case, {@link #setFeatureType(FeatureType)} should create a new list instead of invoking
+     * In such case, {@link #setFeatureType(DefaultFeatureType)} should create a new list instead of invoking
      * {@link FeatureList#clear()} on the existing list.
      */
     private boolean isSharingList;
@@ -178,7 +179,7 @@
      * All methods on the returned list shall be invoked from JavaFX thread.
      */
     final FeatureList getFeatureList() {
-        final ObservableList<Feature> items = getItems();
+        final ObservableList<AbstractFeature> items = getItems();
         if (items instanceof FeatureList) {
             return (FeatureList) items;
         } else {
@@ -191,7 +192,7 @@
      * This method wraps the {@link FeatureList} into an {@link ExpandableList} if needed.
      */
     private ExpandableList getExpandableList() {
-        final ObservableList<Feature> items = getItems();
+        final ObservableList<AbstractFeature> items = getItems();
         if (items instanceof ExpandableList) {
             return (ExpandableList) items;
         } else {
@@ -255,7 +256,7 @@
      * This method clears all rows and replaces all columns by new columns
      * determined from the given type.
      */
-    final void setFeatureType(final FeatureType type) {
+    final void setFeatureType(final DefaultFeatureType type) {
         setPlaceholder(null);
         getItems().clear();
         final boolean update = (type != null) && !type.equals(featureType);
@@ -273,10 +274,10 @@
      * Creates table columns for the current {@link #featureType}.
      */
     private void createColumns() {
-        final Collection<? extends PropertyType> properties = featureType.getProperties(true);
-        final List<TableColumn<Feature,?>> columns = new ArrayList<>(properties.size());
+        final Collection<? extends AbstractIdentifiedType> properties = featureType.getProperties(true);
+        final List<TableColumn<AbstractFeature,?>> columns = new ArrayList<>(properties.size());
         final List<String> multiValued = new ArrayList<>(columns.size());
-        for (final PropertyType pt : properties) {
+        for (final AbstractIdentifiedType pt : properties) {
             /*
              * Get localized text to show in column header. Also remember
              * the plain property name; it will be needed for ValueGetter.
@@ -296,8 +297,8 @@
              *       See comment in class javadoc.
              */
             boolean isMultiValued = false;
-            if (pt instanceof AttributeType<?>) {
-                isMultiValued = ((AttributeType<?>) pt).getMaximumOccurs() > 1;
+            if (pt instanceof DefaultAttributeType<?>) {
+                isMultiValued = ((DefaultAttributeType<?>) pt).getMaximumOccurs() > 1;
             }
             if (isMultiValued) {
                 multiValued.add(name);
@@ -307,7 +308,7 @@
              * gives the whole collection. Fetching a particular element in that collection will
              * be ElementCell's work.
              */
-            final TableColumn<Feature,Object> column = new TableColumn<>(title);
+            final TableColumn<AbstractFeature,Object> column = new TableColumn<>(title);
             column.setCellValueFactory(new ValueGetter(name));
             column.setCellFactory(isMultiValued ? ElementCell::new : ValueCell::new);
             if (AttributeConvention.contains(qualifiedName)) {
@@ -324,7 +325,7 @@
         } else {
             final ExpandableList list = getExpandableList();
             list.setMultivaluedColumns(multiValued);
-            final TableColumn<Feature,Feature> column = new TableColumn<>("▤");
+            final TableColumn<AbstractFeature,AbstractFeature> column = new TableColumn<>("▤");
             column.setCellValueFactory(IdentityValueFactory.instance());
             column.setCellFactory(list);
             column.setReorderable(false);
@@ -342,12 +343,12 @@
 
 
     /**
-     * Given a {@link Feature}, returns the value of the property having the name specified at construction time.
+     * Given a {@code Feature}, returns the value of the property having the name specified at construction time.
      * Note that if the property is multi-valued, then this getter returns the whole collection since we have no
      * easy way to know the current row number. Fetching a particular element in that collection will be done by
      * {@link ExpandedFeature}.
      */
-    private static final class ValueGetter implements Callback<TableColumn.CellDataFeatures<Feature,Object>, ObservableValue<Object>> {
+    private static final class ValueGetter implements Callback<TableColumn.CellDataFeatures<AbstractFeature,Object>, ObservableValue<Object>> {
         /**
          * The name of the feature property for which to fetch values.
          */
@@ -367,9 +368,9 @@
          * This method is invoked by JavaFX when a cell needs to be rendered with a new value.
          */
         @Override
-        public ObservableValue<Object> call(final TableColumn.CellDataFeatures<Feature, Object> cell) {
+        public ObservableValue<Object> call(final TableColumn.CellDataFeatures<AbstractFeature, Object> cell) {
             Object value = null;
-            final Feature feature = cell.getValue();
+            final AbstractFeature feature = cell.getValue();
             if (feature != null) {
                 value = feature.getPropertyValue(name);
             }
@@ -381,13 +382,13 @@
      * A cell displaying a value in {@link FeatureTable}. This base class expects single values.
      * If the property values are collections, then {@link ElementCell} should be used instead.
      */
-    private static class ValueCell extends TableCell<Feature,Object> {
+    private static class ValueCell extends TableCell<AbstractFeature,Object> {
         /**
          * Creates a new cell for feature property value.
          *
          * @param  column  the column where the cell will be shown.
          */
-        ValueCell(final TableColumn<Feature,Object> column) {
+        ValueCell(final TableColumn<AbstractFeature,Object> column) {
             // Column not used at this time, but we need it in method signature.
         }
 
@@ -429,7 +430,7 @@
          *
          * @param  column  the column where the cell will be shown.
          */
-        ElementCell(final TableColumn<Feature,Object> column) {
+        ElementCell(final TableColumn<AbstractFeature,Object> column) {
             super(column);
         }
 
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
index 7e62bff..ce79908 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
@@ -717,7 +717,6 @@
         position.setText(null);
         if (geometry != null) {
             final int dimension = geometry.getDimension();
-            ArgumentChecks.ensureDimensionMatches("sliceExtent", dimension, sliceExtent);
             ArgumentChecks.ensureBetween("xdim", 0,      dimension-1, xdim);
             ArgumentChecks.ensureBetween("ydim", xdim+1, dimension-1, ydim);
             xDimension = xdim;
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesUnderCursor.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesUnderCursor.java
index 8ec168b..5f87df7 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesUnderCursor.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesUnderCursor.java
@@ -36,7 +36,6 @@
 import javafx.scene.control.MenuItem;
 import javax.measure.Unit;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.coverage.CannotEvaluateException;
 import org.opengis.metadata.content.TransferFunctionType;
 import org.apache.sis.referencing.operation.transform.TransferFunction;
 import org.apache.sis.gui.coverage.CoverageCanvas;
@@ -53,6 +52,7 @@
 import org.apache.sis.util.Characters;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.coverage.CannotEvaluateException;
 
 import static java.util.logging.Logger.getLogger;
 
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/IdentificationInfo.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/IdentificationInfo.java
index 0ad5de9..5b039ed 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/IdentificationInfo.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/IdentificationInfo.java
@@ -40,6 +40,7 @@
 import org.opengis.metadata.extent.GeographicDescription;
 import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.metadata.identification.Identification;
+import org.opengis.metadata.identification.DataIdentification;
 import org.opengis.metadata.distribution.Format;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.iso.extent.Extents;
@@ -204,8 +205,8 @@
                         if (isCancelled()) break;
                         if (metadata != null) {
                             for (final Identification id : nonNull(metadata.getIdentificationInfo())) {
-                                if (id != null) {
-                                    for (final Extent extent : id.getExtents()) {
+                                if (id instanceof DataIdentification) {
+                                    for (final Extent extent : ((DataIdentification) id).getExtents()) {
                                         final GeographicBoundingBox b = Extents.getGeographicBoundingBox(extent);
                                         if (b != null) boxes.add(b);
                                     }
@@ -311,7 +312,10 @@
         /*
          * Topic category.
          */
-        addLine(Vocabulary.Keys.TopicCategory, owner.string(nonNull(info.getTopicCategories())));
+        final DataIdentification dataInfo = (info instanceof DataIdentification) ? (DataIdentification) info : null;
+        if (dataInfo != null) {
+            addLine(Vocabulary.Keys.TopicCategory, owner.string(nonNull(dataInfo.getTopicCategories())));
+        }
         /*
          * Select a single, arbitrary date. We take the release or publication date if available.
          * If no publication date is found, fallback on the creation date. If no creation date is
@@ -324,7 +328,7 @@
                 final Date cd = c.getDate();
                 if (cd != null) {
                     final DateType type = c.getDateType();
-                    if (DateType.PUBLICATION.equals(type) || DateType.RELEASED.equals(type)) {
+                    if (DateType.PUBLICATION.equals(type) || DateType.valueOf("RELEASED").equals(type)) {
                         label = Vocabulary.Keys.PublicationDate;
                         date  = cd;
                         break;                      // Take the first publication or release date.
@@ -345,18 +349,17 @@
          * of the next section, "Spatial representation". For that reason we put it close to
          * that next section, i.e. last in this section but just before the map.
          */
-        addLine(Vocabulary.Keys.TypeOfResource, owner.string(nonNull(info.getSpatialRepresentationTypes())));
+        if (dataInfo != null) {
+            addLine(Vocabulary.Keys.TypeOfResource, owner.string(nonNull(dataInfo.getSpatialRepresentationTypes())));
+        }
         /*
          * Resource format. Current implementation shows only the first format found.
          */
         for (final Format format : nonNull(info.getResourceFormats())) {
-            final Citation c = format.getFormatSpecificationCitation();
-            if (c != null) {
-                text = owner.string(c.getTitle());
-                if (text != null) {
-                    addLine(Vocabulary.Keys.Format, text);
-                    break;
-                }
+            text = owner.string(format.getSpecification());
+            if (text != null) {
+                addLine(Vocabulary.Keys.Format, text);
+                break;
             }
         }
         /*
@@ -367,7 +370,7 @@
          */
         text = null;
         Identifier identifier = null;
-        for (final Extent extent : nonNull(info.getExtents())) {
+        if (dataInfo != null) for (final Extent extent : nonNull(dataInfo.getExtents())) {
             if (extent != null) {
                 if (text == null) {
                     text = owner.string(extent.getDescription());
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java
index 7eb7382..5acaf0c 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java
@@ -34,8 +34,8 @@
 import javafx.scene.image.Image;
 import javafx.scene.layout.Region;
 import javafx.scene.layout.VBox;
+import org.opengis.util.CodeList;
 import org.opengis.metadata.Metadata;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.gui.BackgroundThreads;
 import org.apache.sis.internal.gui.ExceptionReporter;
@@ -354,9 +354,9 @@
     /**
      * Returns all code lists in a comma-separated list.
      */
-    final String string(final Collection<? extends ControlledVocabulary> codes) {
+    final String string(final Collection<? extends CodeList<?>> codes) {
         final StringJoiner buffer = new StringJoiner(", ");
-        for (final ControlledVocabulary c : codes) {
+        for (final CodeList<?> c : codes) {
             final String text = string(Types.getCodeTitle(c));
             if (text != null) buffer.add(text);
         }
diff --git a/application/sis-openoffice/pom.xml b/application/sis-openoffice/pom.xml
index 5f82836..b58e410 100644
--- a/application/sis-openoffice/pom.xml
+++ b/application/sis-openoffice/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>application</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
@@ -83,7 +83,7 @@
   <dependencies>
     <dependency>
       <groupId>org.opengis</groupId>
-      <artifactId>geoapi-pending</artifactId>
+      <artifactId>geoapi</artifactId>
     </dependency>
     <dependency>
       <groupId>org.apache.sis.core</groupId>
diff --git a/application/sis-openoffice/src/main/unopkg/build-instruction.html b/application/sis-openoffice/src/main/unopkg/build-instruction.html
index 1bfbd75..47ba04a 100644
--- a/application/sis-openoffice/src/main/unopkg/build-instruction.html
+++ b/application/sis-openoffice/src/main/unopkg/build-instruction.html
@@ -100,7 +100,7 @@
 <h2>Test in Apache OpenOffice:</h2>
 <p>Launch:</p>
 <blockquote><pre>cd target
-unopkg add apache-sis-1.1-SNAPSHOT.oxt --log-file log.txt
+unopkg add apache-sis-1.3-SNAPSHOT.oxt --log-file log.txt
 scalc -env:RTL_LOGFILE=log.txt</pre></blockquote>
 
 <p>If not already done, configure Java runtime with
diff --git a/application/sis-webapp/pom.xml b/application/sis-webapp/pom.xml
index 762493c..5f93808 100644
--- a/application/sis-webapp/pom.xml
+++ b/application/sis-webapp/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>application</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
   <groupId>org.apache.sis.application</groupId>
diff --git a/cloud/pom.xml b/cloud/pom.xml
index d91b8b4..7a32346 100644
--- a/cloud/pom.xml
+++ b/cloud/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/cloud/sis-cloud-aws/pom.xml b/cloud/sis-cloud-aws/pom.xml
index 3be2ec6..92cedeb 100644
--- a/cloud/sis-cloud-aws/pom.xml
+++ b/cloud/sis-cloud-aws/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>cloud</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/core/pom.xml b/core/pom.xml
index cfda747..ed07f43 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
@@ -171,7 +171,7 @@
     </dependency>
     <dependency>
       <groupId>org.opengis</groupId>
-      <artifactId>geoapi-pending</artifactId>
+      <artifactId>geoapi</artifactId>
     </dependency>
 
     <!-- Test dependencies -->
@@ -192,7 +192,6 @@
     <module>sis-referencing</module>
     <module>sis-referencing-by-identifiers</module>
     <module>sis-feature</module>
-    <module>sis-cql</module>
     <module>sis-portrayal</module>
   </modules>
 
diff --git a/core/sis-build-helper/pom.xml b/core/sis-build-helper/pom.xml
index 3030641..6f1b824 100644
--- a/core/sis-build-helper/pom.xml
+++ b/core/sis-build-helper/pom.xml
@@ -32,7 +32,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
     <relativePath>../../pom.xml</relativePath>
   </parent>
 
diff --git a/core/sis-cql/pom.xml b/core/sis-cql/pom.xml
deleted file mode 100644
index 40c207a..0000000
--- a/core/sis-cql/pom.xml
+++ /dev/null
@@ -1,145 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
-  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.
--->
-
-<project xmlns              = "http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi          = "http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0
-                               http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>org.apache.sis</groupId>
-    <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
-  </parent>
-
-
-  <!-- ===========================================================
-           Module Description
-       =========================================================== -->
-  <groupId>org.apache.sis.core</groupId>
-  <artifactId>sis-cql</artifactId>
-  <name>Apache SIS CQL</name>
-  <description>
-    CQL parser.
-  </description>
-
-
-  <!-- ===========================================================
-           Developers and Contributors
-       =========================================================== -->
-  <developers>
-    <developer>
-      <name>Johann Sorel</name>
-      <id>jsorel</id>
-      <organization>Geomatys</organization>
-      <organizationUrl>https://www.geomatys.com</organizationUrl>
-      <timezone>+1</timezone>
-      <roles>
-        <role>developer</role>
-      </roles>
-    </developer>
-  </developers>
-
-
-  <!-- ===========================================================
-           Build configuration
-       =========================================================== -->
-  <build>
-    <plugins>
-      <!-- Anticipation for Java 9 -->
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <configuration>
-          <archive>
-            <manifestEntries>
-              <Automatic-Module-Name>
-                org.apache.sis.cql
-              </Automatic-Module-Name>
-            </manifestEntries>
-          </archive>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.antlr</groupId>
-        <artifactId>antlr4-maven-plugin</artifactId>
-        <version>4.10.1</version>
-        <executions>
-          <execution>
-            <id>run antlr</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>antlr4</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-checkstyle-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>check</goal>
-            </goals>
-            <configuration>
-              <skip>true</skip>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-
-
-  <!-- ===========================================================
-           Dependencies
-       =========================================================== -->
-  <dependencies>
-    <dependency>
-      <groupId>org.apache.sis.core</groupId>
-      <artifactId>sis-feature</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.locationtech.jts</groupId>
-      <artifactId>jts-core</artifactId>
-      <optional>true</optional>
-    </dependency>
-    <dependency>
-      <groupId>org.antlr</groupId>
-      <artifactId>antlr4-runtime</artifactId>
-      <version>4.10.1</version>
-      <scope>compile</scope>
-    </dependency>
-
-    <!-- Test dependencies -->
-    <dependency>
-      <groupId>org.apache.sis.core</groupId>
-      <artifactId>sis-utility</artifactId>
-      <version>${project.version}</version>
-      <type>test-jar</type>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-
-</project>
diff --git a/core/sis-cql/src/main/antlr4/org/apache/sis/internal/cql/CQL.g4 b/core/sis-cql/src/main/antlr4/org/apache/sis/internal/cql/CQL.g4
deleted file mode 100644
index c2e3fcf..0000000
--- a/core/sis-cql/src/main/antlr4/org/apache/sis/internal/cql/CQL.g4
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * 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.
- */
-grammar CQL;
-
-options {
-    language = Java;
-}
-
-//-----------------------------------------------------------------//
-// LEXER
-//-----------------------------------------------------------------//
-
-
-// GLOBAL STUFF ---------------------------------------
-
-COMMA 	: ',' ;
-WS  :   ( ' ' | '\t' | '\r'| '\n' ) -> skip;
-UNARY : '+' | '-' ;
-MULT : '*' ;
-DIV : '/' ;
-fragment DIGIT : '0'..'9' ;
-
-// caseinsensitive , possible alternative solution ?
-fragment A: ('a'|'A');
-fragment B: ('b'|'B');
-fragment C: ('c'|'C');
-fragment D: ('d'|'D');
-fragment E: ('e'|'E');
-fragment F: ('f'|'F');
-fragment G: ('g'|'G');
-fragment H: ('h'|'H');
-fragment I: ('i'|'I');
-fragment J: ('j'|'J');
-fragment K: ('k'|'K');
-fragment L: ('l'|'L');
-fragment M: ('m'|'M');
-fragment N: ('n'|'N');
-fragment O: ('o'|'O');
-fragment P: ('p'|'P');
-fragment Q: ('q'|'Q');
-fragment R: ('r'|'R');
-fragment S: ('s'|'S');
-fragment T: ('t'|'T');
-fragment U: ('u'|'U');
-fragment V: ('v'|'V');
-fragment W: ('w'|'W');
-fragment X: ('x'|'X');
-fragment Y: ('y'|'Y');
-fragment Z: ('z'|'Z');
-fragment LETTER : ~('0'..'9' | ' ' | '\t' | '\r'| '\n' | ',' | '-' | '+' | '*' | '/' | '(' | ')' | '=' | '>' | '<');
-
-LPAREN : '(';
-RPAREN : ')';
-
-
-//LITERALS  ----------------------------------------------
-
-TEXT :   '\'' ( ESC_SEQ | ~('\'') )* '\'' ;
-INT : DIGIT+ ;
-
-FLOAT
-    :   ('0'..'9')+ '.' ('0'..'9')* EXPONENT?
-    |   '.' ('0'..'9')+ EXPONENT?
-    |   ('0'..'9')+ EXPONENT
-    ;
-
-
-// FILTERING OPERAND -----------------------------------
-COMPARE
-	: EQUALABOVE
-	| EQUALUNDER
-	| NOTEQUAL
-	| EQUAL
-	| ABOVE
-	| UNDER
-	;
-fragment EQUALABOVE : '>=' ;
-fragment EQUALUNDER : '<=' ;
-fragment NOTEQUAL   : '<>' ;
-fragment EQUAL      : '=' ;
-fragment ABOVE      : '>' ;
-fragment UNDER      : '<' ;
-LIKE    : L I K E;
-ILIKE   : I L I K E;
-
-IS      : I S ;
-NULL    : N U L L ;
-BETWEEN : B E T W E E N;
-IN      : I N;
-
-
-
-// LOGIC ----------------------------------------------
-AND : A N D;
-OR  : O R ;
-NOT : N O T ;
-
-// GEOMETRIC TYPES AND FILTERS ------------------------
-POINT               : P O I N T ;
-LINESTRING          : L I N E S T R I N G ;
-POLYGON             : P O L Y G O N ;
-MPOINT              : M U L T I P O I N T ;
-MLINESTRING         : M U L T I L I N E S T R I N G ;
-MPOLYGON            : M U L T I P O L Y G O N ;
-GEOMETRYCOLLECTION  : G E O M E T R Y C O L L E C T I O N ;
-ENVELOPE            : E N V E L O P E ;
-EMPTY               : E M P T Y ;
-
-BBOX        : B B O X ;
-BEYOND      : B E Y O N D ;
-CONTAINS    : C O N T A I N S ;
-CROSSES     : C R O S S E S;
-DISJOINT    : D I S J O I N T ;
-DWITHIN     : D W I T H I N ;
-EQUALS      : E Q U A L S ;
-INTERSECTS  : I N T E R S E C T S;
-OVERLAPS    : O V E R L A P S;
-TOUCHES     : T O U C H E S;
-WITHIN      : W I T H I N ;
-
-// TEMPORAL TYPES AND FILTERS
-
-DATE : DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT 'T' DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ('.' DIGIT+)? 'Z';
-DURATION_P : P (INT 'Y')? (INT 'M')? (INT 'D')? (INT 'H')? (INT 'M')? (INT 'S')?;
-DURATION_T : P T (INT 'H')? (INT 'M')? (INT 'S')?;
-
-AFTER		: A F T E R ;
-ANYINTERACTS	: A N Y I N T E R A C T S ;
-BEFORE		: B E F O R E ;
-BEGINS		: B E G I N S ;
-BEGUNBY		: B E G U N B Y ;
-DURING		: D U R I N G ;
-ENDEDBY		: E N D E D B Y ;
-ENDS		: E N D S ;
-MEETS		: M E E T S ;
-METBY		: M E T B Y ;
-OVERLAPPEDBY	: O V E R L A P P E D B Y ;
-TCONTAINS	: T C O N T A I N S ;
-TEQUALS		: T E Q U A L S ;
-TOVERLAPS	: T O V E R L A P S ;
-
-// QUERY ---------------------------------------------
-
-SELECT : S E L E C T ;
-WHERE : W H E R E ;
-LIMIT : L I M I T ;
-OFFSET : O F F S E T ;
-AS : A S ;
-ORDER : O R D E R ;
-BY : B Y ;
-ASC : A S C ;
-DESC : D E S C ;
-
-// PROPERTY NAME -------------------------------------
-PROPERTY_NAME    	:  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'    ;
-NAME   	: LETTER (DIGIT|LETTER)* ;
-
-
-// FRAGMENT -------------------------------------------
-
-fragment EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;
-fragment HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;
-
-fragment
-ESC_SEQ
-    :   '\\' ('b'|'t'|'n'|'f'|'r'|'"'|'\''|'\\')
-    |   UNICODE_ESC
-    |   OCTAL_ESC
-    ;
-
-fragment
-OCTAL_ESC
-    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
-    |   '\\' ('0'..'7') ('0'..'7')
-    |   '\\' ('0'..'7')
-    ;
-
-fragment
-UNICODE_ESC
-    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
-    ;
-
-
-
-
-//-----------------------------------------------------------------//
-// PARSER
-//-----------------------------------------------------------------//
-
-expressionNum : INT | FLOAT ;
-expressionUnary : UNARY? expressionNum ;
-
-coordinate          : expressionUnary expressionUnary ;
-coordinateSerie    : LPAREN coordinate (COMMA coordinate)*  RPAREN ;
-coordinateSeries   : LPAREN coordinateSerie (COMMA coordinateSerie)* RPAREN;
-
-expressionGeometry
-	: POINT ( EMPTY | coordinateSerie )
-	| LINESTRING ( EMPTY | coordinateSerie )
-	| POLYGON ( EMPTY | coordinateSeries )
-	| MPOINT ( EMPTY | coordinateSerie )
-	| MLINESTRING  ( EMPTY | coordinateSeries )
-	| MPOLYGON ( EMPTY | LPAREN coordinateSeries (COMMA coordinateSeries)* RPAREN )
-        | GEOMETRYCOLLECTION ( EMPTY | (LPAREN expressionGeometry (COMMA expressionGeometry)* RPAREN) )
-        | ENVELOPE ( EMPTY | (LPAREN expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary RPAREN) )
-	;
-
-expressionFctParam
-        : expression (COMMA expression)*
-        ;
-
-expressionTerm
-	: TEXT
-	| expressionUnary
-	| PROPERTY_NAME
-	| DATE
-	| DURATION_P
-	| DURATION_T
-	| NAME (LPAREN expressionFctParam? RPAREN)?
-	| expressionGeometry
-	| LPAREN expression RPAREN
-	;
-
-expression : expression (MULT|DIV) expression
-           | expression UNARY expression
-           | expressionTerm
-           ;
-
-filterGeometry
-        : BBOX LPAREN expression COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary (COMMA TEXT)? RPAREN
-        | BEYOND LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-        | CONTAINS LPAREN expression COMMA expression RPAREN
-        | CROSSES LPAREN expression COMMA expression RPAREN
-        | DISJOINT LPAREN expression COMMA expression RPAREN
-        | DWITHIN LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-        | EQUALS LPAREN expression COMMA expression RPAREN
-        | INTERSECTS LPAREN expression COMMA expression RPAREN
-        | OVERLAPS LPAREN expression COMMA expression RPAREN
-        | TOUCHES LPAREN expression COMMA expression RPAREN
-        | WITHIN LPAREN expression COMMA expression RPAREN
-        ;
-
-filterTerm 	: expression
-                    (
-                              COMPARE  expression
-                            | NOT? IN LPAREN (expressionFctParam )?  RPAREN
-                            | BETWEEN expression AND expression
-                            | NOT? LIKE expression
-                            | NOT? ILIKE expression
-                            | IS NOT? NULL
-                            | AFTER  expression
-                            | ANYINTERACTS expression
-                            | BEFORE expression
-                            | BEGINS expression
-                            | BEGUNBY expression
-                            | DURING expression
-                            | ENDEDBY expression
-                            | ENDS expression
-                            | MEETS expression
-                            | METBY expression
-                            | OVERLAPPEDBY expression
-                            | TCONTAINS expression
-                            | TEQUALS expression
-                            | TOVERLAPS expression
-                    )
-                | filterGeometry
-                ;
-
-filter : filter (AND filter)+
-       | filter (OR filter )+
-       | LPAREN filter RPAREN
-       | NOT (filterTerm | (LPAREN filter RPAREN) )
-       | filterTerm
-       ;
-
-filterOrExpression : filter | expression ;
-
-sortprop : expression (ASC | DESC)? ;
-orderby : ORDER BY sortprop (COMMA sortprop)* ;
-limit : LIMIT INT ;
-offset : OFFSET INT ;
-where : WHERE filter ;
-projection : expression (AS TEXT)? ;
-query : SELECT (MULT | (projection (COMMA projection)*)) where? orderby? offset? limit?;
diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java b/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
deleted file mode 100644
index 83cb251..0000000
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
+++ /dev/null
@@ -1,865 +0,0 @@
-/*
- * 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.cql;
-
-import java.time.Duration;
-import java.time.Period;
-import java.util.List;
-import java.util.ArrayList;
-import java.time.temporal.TemporalAccessor;
-import javax.measure.Unit;
-import javax.measure.quantity.Length;
-import org.antlr.v4.runtime.tree.ParseTree;
-import org.antlr.v4.runtime.tree.TerminalNode;
-import org.apache.sis.measure.Units;
-import org.apache.sis.measure.Quantities;
-import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.internal.cql.AntlrCQL;
-import org.apache.sis.internal.cql.CQLParser.CoordinateContext;
-import org.apache.sis.internal.cql.CQLParser.CoordinateSerieContext;
-import org.apache.sis.internal.cql.CQLParser.CoordinateSeriesContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionFctParamContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionGeometryContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionNumContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionTermContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionUnaryContext;
-import org.apache.sis.internal.cql.CQLParser.FilterContext;
-import org.apache.sis.internal.cql.CQLParser.FilterGeometryContext;
-import org.apache.sis.internal.cql.CQLParser.FilterTermContext;
-import org.apache.sis.internal.util.StandardDateFormat;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.CoordinateSequence;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.util.FactoryException;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.filter.ValueReference;
-
-import static org.apache.sis.internal.cql.CQLParser.*;
-import org.opengis.filter.Literal;
-import org.opengis.filter.SortOrder;
-import org.opengis.filter.SortProperty;
-
-
-/**
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final class CQL {
-
-    private static final GeometryFactory GF = org.apache.sis.internal.feature.jts.Factory.INSTANCE.factory(false);
-
-    private CQL() {
-    }
-
-    public static Expression<Feature,?> parseExpression(String cql) throws CQLException {
-        return parseExpression(cql, null);
-    }
-
-    public static Expression<Feature,?> parseExpression(String cql, FilterFactory<Feature,?,?> factory) throws CQLException {
-        final Object obj = AntlrCQL.compileExpression(cql);
-        Expression<Feature,?> result = null;
-        if (obj instanceof ExpressionContext) {
-            ParseTree tree = (ParseTree) obj;
-            if (factory == null) {
-                factory = DefaultFilterFactory.forFeatures();
-            }
-            result = convertExpression(tree, factory);
-        }
-        return result;
-    }
-
-    public static Filter<? super Feature> parseFilter(String cql) throws CQLException {
-        return parseFilter(cql, null);
-    }
-
-    public static Filter<? super Feature> parseFilter(String cql, FilterFactory<Feature,Object,Object> factory) throws CQLException {
-        cql = cql.trim();
-
-        // Bypass parsing for inclusive filter.
-        if (cql.isEmpty() || "*".equals(cql)) {
-            return Filter.include();
-        }
-        final Object obj = AntlrCQL.compileFilter(cql);
-        Filter<? super Feature> result = null;
-        if (obj instanceof FilterContext) {
-            ParseTree tree = (ParseTree) obj;
-            if (factory == null) {
-                factory = DefaultFilterFactory.forFeatures();
-            }
-            result = convertFilter(tree, factory);
-        }
-        return result;
-    }
-
-    public static Query parseQuery(String cql) throws CQLException {
-        return parseQuery(cql, null);
-    }
-
-    public static Query parseQuery(String cql, FilterFactory<Feature,Object,Object> factory) throws CQLException {
-        final Object obj = AntlrCQL.compileQuery(cql);
-        Query result = null;
-        if (obj instanceof QueryContext) {
-            ParseTree tree = (ParseTree) obj;
-            if (factory == null) {
-                factory = DefaultFilterFactory.forFeatures();
-            }
-            result = convertQuery(tree, factory);
-        }
-        return result;
-    }
-
-    public static String write(final Filter<Feature> filter) {
-        final StringBuilder sb = new StringBuilder();
-        FilterToCQLVisitor.INSTANCE.visit(filter, sb);
-        return sb.toString();
-    }
-
-    public static String write(final Expression<Feature,?> exp) {
-        final StringBuilder sb = new StringBuilder();
-        FilterToCQLVisitor.INSTANCE.visit(exp, sb);
-        return sb.toString();
-    }
-
-    public static String write(final Query query) {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("SELECT ");
-        if (query.projections.isEmpty()) {
-            sb.append('*');
-        } else {
-            for (int i = 0, n = query.projections.size(); i < n; i++) {
-                Query.Projection p = query.projections.get(i);
-                if (i != 0) sb.append(", ");
-                sb.append(write(p.expression));
-                if (p.alias != null && !p.alias.isEmpty()) {
-                    sb.append(" AS '");
-                    sb.append(p.alias);
-                    sb.append('\'');
-                }
-            }
-        }
-        if (query.filter != null) {
-            sb.append(" WHERE ");
-            sb.append(write(query.filter));
-        }
-
-        if (!query.sortby.isEmpty()) {
-            sb.append(" ORDER BY ");
-            for (int i = 0, n = query.sortby.size(); i < n; i++) {
-                SortProperty p = query.sortby.get(i);
-                if (i != 0) sb.append(", ");
-                sb.append(write(p.getValueReference()));
-                switch (p.getSortOrder()) {
-                    case ASCENDING : sb.append(" ASC"); break;
-                    case DESCENDING : sb.append(" DESC"); break;
-                }
-            }
-        }
-
-        if (query.offset != null) {
-            sb.append(" OFFSET ");
-            sb.append(query.offset);
-        }
-        if (query.limit != null) {
-            sb.append(" LIMIT ");
-            sb.append(query.limit);
-        }
-
-        return sb.toString();
-    }
-
-    /**
-     * Convert the given tree in an Expression.
-     */
-    private static Expression<Feature,?> convertExpression(ParseTree tree, FilterFactory<Feature,?,?> ff) throws CQLException {
-        if (tree instanceof ExpressionContext) {
-            //: expression MULT expression
-            //| expression UNARY expression
-            //| expressionTerm
-            if (tree.getChildCount() == 3) {
-                final String operand = tree.getChild(1).getText();
-                // TODO: unsafe cast.
-                final Expression<? super Feature, ? extends Number> left  = (Expression<? super Feature, ? extends Number>) convertExpression(tree.getChild(0), ff);
-                final Expression<? super Feature, ? extends Number> right = (Expression<? super Feature, ? extends Number>) convertExpression(tree.getChild(2), ff);
-                if ("*".equals(operand)) {
-                    return ff.multiply(left, right);
-                } else if ("/".equals(operand)) {
-                    return ff.divide(left, right);
-                } else if ("+".equals(operand)) {
-                    return ff.add(left, right);
-                } else if ("-".equals(operand)) {
-                    return ff.subtract(left, right);
-                }
-            } else {
-                return convertExpression(tree.getChild(0), ff);
-            }
-        } //        else if(tree instanceof ExpressionStringContext){
-        //            //strip start and end '
-        //            final String text = tree.getText();
-        //            return ff.literal(text.substring(1, text.length()-1));
-        //        }
-        else if (tree instanceof ExpressionTermContext) {
-            //: expressionString
-            //| expressionUnary
-            //| PROPERTY_NAME
-            //| DATE
-            //| DURATION_P
-            //| DURATION_T
-            //| NAME (LPAREN expressionFctParam? RPAREN)?
-            //| expressionGeometry
-            //| LPAREN expression RPAREN
-
-            //: TEXT
-            //| expressionUnary
-            //| PROPERTY_NAME
-            //| DATE
-            //| DURATION_P
-            //| DURATION_T
-            //| expressionGeometry
-            final ExpressionTermContext exp = (ExpressionTermContext) tree;
-            if (exp.getChildCount() == 1) {
-                return convertExpression(tree.getChild(0), ff);
-            }
-            // LPAREN expression RPAREN
-            if (exp.expression() != null) {
-                return convertExpression(exp.expression(), ff);
-            }
-            // NAME (LPAREN expressionFctParam? RPAREN)?
-            if (exp.NAME() != null) {
-                final String name = exp.NAME().getText();
-                final ExpressionFctParamContext prm = exp.expressionFctParam();
-                if (prm == null) {
-                    //handle as property name
-                    return ff.property(name);
-                }
-                // Handle as a function.
-                final List<ExpressionContext> params = prm.expression();
-                final List<Expression<Feature,?>> exps = new ArrayList<>();
-                for (int i = 0, n = params.size(); i < n; i++) {
-                    exps.add(convertExpression(params.get(i), ff));
-                }
-                return ff.function(name, exps.toArray(new Expression[exps.size()]));
-            }
-        } else if (tree instanceof ExpressionUnaryContext) {
-            //: UNARY? expressionNum ;
-            final ExpressionUnaryContext exp = (ExpressionUnaryContext) tree;
-            return ff.literal(unaryAsNumber(exp));
-
-        } else if (tree instanceof ExpressionNumContext) {
-            //: INT | FLOAT ;
-            return convertExpression(tree.getChild(0), ff);
-        } else if (tree instanceof TerminalNode) {
-            final TerminalNode exp = (TerminalNode) tree;
-            switch (exp.getSymbol().getType()) {
-                case PROPERTY_NAME: {
-                    // strip start and end "
-                    final String text = tree.getText();
-                    return ff.property(text.substring(1, text.length() - 1));
-                }
-                case NAME:  return ff.property(tree.getText());
-                case INT:   return ff.literal(Integer.valueOf(tree.getText()));
-                case FLOAT: return ff.literal(Double.valueOf(tree.getText()));
-                case DATE: {
-                    TemporalAccessor ta = StandardDateFormat.FORMAT.parse(tree.getText());
-                    return ff.literal(ta);
-                    // TODO! return ff.literal(TemporalUtilities.getTimeInMillis(tree.getText()));
-                }
-                case TEXT: {
-                    // strip start and end '
-                    String text = tree.getText();
-                    text = text.replaceAll("\\\\'", "'");
-                    return ff.literal(text.substring(1, text.length() - 1));
-                }
-                case DURATION_P :
-                    return ff.literal(Period.parse(tree.getText()));
-                case DURATION_T :
-                    return ff.literal(Duration.parse(tree.getText()));
-            }
-        } else if (tree instanceof ExpressionGeometryContext) {
-            //: POINT ( EMPTY | coordinateSerie )
-            //| LINESTRING ( EMPTY | coordinateSerie )
-            //| POLYGON ( EMPTY | coordinateSeries )
-            //| MPOINT ( EMPTY | coordinateSerie )
-            //| MLINESTRING  ( EMPTY | coordinateSeries )
-            //| MPOLYGON ( EMPTY | LPAREN coordinateSeries (COMMA coordinateSeries)* RPAREN )
-            //| GEOMETRYCOLLECTION ( EMPTY | (LPAREN expressionGeometry (COMMA expressionGeometry)* RPAREN) )
-            //| ENVELOPE ( EMPTY | (LPAREN expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary RPAREN) )
-            final ExpressionGeometryContext exp = (ExpressionGeometryContext) tree;
-            switch (((TerminalNode) exp.getChild(0)).getSymbol().getType()) {
-                case POINT: {
-                    final ParseTree st = tree.getChild(1);
-                    final CoordinateSequence cs;
-                    if (isEmptyToken(st)) {
-                        cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
-                    } else {
-                        cs = parseSequence(st);
-                    }
-                    final Geometry geom = GF.createPoint(cs);
-                    return ff.literal(geom);
-                }
-                case LINESTRING: {
-                    final ParseTree st = tree.getChild(1);
-                    final CoordinateSequence cs;
-                    if (isEmptyToken(st)) {
-                        cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
-                    } else {
-                        cs = parseSequence(st);
-                    }
-                    final Geometry geom = GF.createLineString(cs);
-                    return ff.literal(geom);
-                }
-                case POLYGON: {
-                    final ParseTree st = tree.getChild(1);
-                    final Geometry geom;
-                    if (isEmptyToken(st)) {
-                        geom = GF.createPolygon(GF.createLinearRing(new Coordinate[0]), new LinearRing[0]);
-                    } else {
-                        final CoordinateSeriesContext series = (CoordinateSeriesContext) st;
-                        final List<CoordinateSerieContext> subs = series.coordinateSerie();
-                        final LinearRing contour = GF.createLinearRing(parseSequence(subs.get(0)));
-                        final int n = subs.size();
-                        final LinearRing[] holes = new LinearRing[n - 1];
-                        for (int i = 1; i < n; i++) {
-                            holes[i - 1] = GF.createLinearRing(parseSequence(subs.get(i)));
-                        }
-                        geom = GF.createPolygon(contour, holes);
-                    }
-                    return ff.literal(geom);
-                }
-                case MPOINT: {
-                    final ParseTree st = tree.getChild(1);
-                    final CoordinateSequence cs;
-                    if (isEmptyToken(st)) {
-                        cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
-                    } else {
-                        cs = parseSequence(st);
-                    }
-                    final Geometry geom = GF.createMultiPoint(cs);
-                    return ff.literal(geom);
-                }
-                case MLINESTRING: {
-                    final ParseTree st = tree.getChild(1);
-                    final Geometry geom;
-                    if (isEmptyToken(st)) {
-                        geom = GF.createMultiLineString(new LineString[0]);
-                    } else {
-                        final CoordinateSeriesContext series = (CoordinateSeriesContext) st;
-                        final List<CoordinateSerieContext> subs = series.coordinateSerie();
-                        final int n = subs.size();
-                        final LineString[] strings = new LineString[n];
-                        for (int i = 0; i < n; i++) {
-                            strings[i] = GF.createLineString(parseSequence(subs.get(i)));
-                        }
-                        geom = GF.createMultiLineString(strings);
-                    }
-                    return ff.literal(geom);
-                }
-                case MPOLYGON: {
-                    final ParseTree st = tree.getChild(1);
-                    final Geometry geom;
-                    if (isEmptyToken(st)) {
-                        geom = GF.createMultiPolygon(new Polygon[0]);
-                    } else {
-                        final List<CoordinateSeriesContext> eles = exp.coordinateSeries();
-                        final int n = eles.size();
-                        final Polygon[] polygons = new Polygon[n];
-                        for (int i=0; i<n; i++) {
-                            final CoordinateSeriesContext polyTree = eles.get(i);
-                            final List<CoordinateSerieContext> subs = polyTree.coordinateSerie();
-                            final LinearRing contour = GF.createLinearRing(parseSequence(subs.get(0)));
-                            final int hn = subs.size();
-                            final LinearRing[] holes = new LinearRing[hn - 1];
-                            for (int j=1; j<hn; j++) {
-                                holes[j-1] = GF.createLinearRing(parseSequence(subs.get(j)));
-                            }
-                            final Polygon poly = GF.createPolygon(contour, holes);
-                            polygons[i] = poly;
-                        }
-                        geom = GF.createMultiPolygon(polygons);
-                    }
-                    return ff.literal(geom);
-                }
-                case GEOMETRYCOLLECTION: {
-                    final ParseTree st = tree.getChild(1);
-                    final Geometry geom;
-                    if (isEmptyToken(st)) {
-                        geom = GF.createGeometryCollection(new Geometry[0]);
-                    } else {
-                        final List<ExpressionGeometryContext> eles = exp.expressionGeometry();
-                        final int n = eles.size();
-                        final Geometry[] subs = new Geometry[n];
-                        for (int i=0; i<n; i++) {
-                            final ParseTree subTree = eles.get(i);
-                            final Geometry sub = (Geometry) convertExpression(subTree, ff).apply(null);
-                            subs[i] = sub;
-                        }
-                        geom = GF.createGeometryCollection(subs);
-                    }
-                    return ff.literal(geom);
-                }
-                case ENVELOPE: {
-                    final ParseTree st = tree.getChild(1);
-                    final Geometry geom;
-                    if (isEmptyToken(st)) {
-                        geom = GF.createPolygon(GF.createLinearRing(new Coordinate[0]), new LinearRing[0]);
-                    } else {
-                        final List<ExpressionUnaryContext> unaries = exp.expressionUnary();
-                        final double west  = unaryAsNumber(unaries.get(0)).doubleValue();
-                        final double east  = unaryAsNumber(unaries.get(1)).doubleValue();
-                        final double north = unaryAsNumber(unaries.get(2)).doubleValue();
-                        final double south = unaryAsNumber(unaries.get(3)).doubleValue();
-                        final LinearRing contour = GF.createLinearRing(new Coordinate[]{
-                            new Coordinate(west, north),
-                            new Coordinate(east, north),
-                            new Coordinate(east, south),
-                            new Coordinate(west, south),
-                            new Coordinate(west, north)
-                        });
-                        geom = GF.createPolygon(contour, new LinearRing[0]);
-                    }
-                    return ff.literal(geom);
-                }
-            }
-            return convertExpression(tree.getChild(0), ff);
-        }
-        throw new CQLException("Unreconized expression : type=" + tree.getText());
-    }
-
-    private static boolean isEmptyToken(ParseTree tree) {
-        return tree instanceof TerminalNode && ((TerminalNode) tree).getSymbol().getType() == EMPTY;
-    }
-
-    private static Number unaryAsNumber(ExpressionUnaryContext exp) {
-        //: UNARY? expressionNum ;
-        final boolean negate = (exp.UNARY() != null && exp.UNARY().getSymbol().getText().equals("-"));
-        final ExpressionNumContext num = exp.expressionNum();
-        if (num.INT() != null) {
-            int val = Integer.valueOf(num.INT().getText());
-            return negate ? -val : val;
-        } else {
-            double val = Double.valueOf(num.FLOAT().getText());
-            return negate ? -val : val;
-        }
-    }
-
-    private static CoordinateSequence parseSequence(ParseTree tree) {
-        final CoordinateSerieContext exp = (CoordinateSerieContext) tree;
-        final List<CoordinateContext> lst = exp.coordinate();
-        final int size = lst.size();
-        final Coordinate[] coords = new Coordinate[size];
-        for (int i = 0; i < size; i++) {
-            final CoordinateContext cc = lst.get(i);
-            coords[i] = new Coordinate(
-                    unaryAsNumber(cc.expressionUnary(0)).doubleValue(),
-                    unaryAsNumber(cc.expressionUnary(1)).doubleValue());
-        }
-        return GF.getCoordinateSequenceFactory().create(coords);
-    }
-
-    private static Unit<Length> parseLengthUnit(final Expression<Feature,?> unitExp) {
-        Object value = unitExp.apply(null);
-        if (value != null) {
-            return Units.ensureLinear(Units.valueOf(value.toString()));
-        }
-        if (unitExp instanceof ValueReference<?,?>) {
-            value = ((ValueReference<?,?>) unitExp).getXPath();
-        }
-        throw new IllegalArgumentException("Unit `" + value + "` is not a literal.");
-    }
-
-    /**
-     * Convert the given tree in a Filter.
-     */
-    private static Filter<? super Feature> convertFilter(ParseTree tree, FilterFactory<Feature,Object,Object> ff) throws CQLException {
-        if (tree instanceof FilterContext) {
-            //: filter (AND filter)+
-            //| filter (OR filter)+
-            //| LPAREN filter RPAREN
-            //| NOT filterTerm
-            //| filterTerm
-
-            final FilterContext exp = (FilterContext) tree;
-
-            //| filterTerm
-            if (exp.getChildCount() == 1) {
-                return convertFilter(tree.getChild(0), ff);
-            } else if (exp.NOT() != null) {
-                //| NOT (filterTerm | ( LPAREN filter RPAREN ))
-                if (exp.filterTerm() != null) {
-                    return ff.not(convertFilter(exp.filterTerm(), ff));
-                } else {
-                    return ff.not(convertFilter(exp.filter(0), ff));
-                }
-
-            } else if (!exp.AND().isEmpty()) {
-                //: filter (AND filter)+
-                final List<Filter<? super Feature>> subs = new ArrayList<>();
-                for (FilterContext f : exp.filter()) {
-                    final Filter<? super Feature> sub = convertFilter(f, ff);
-                    if (sub.getOperatorType() == LogicalOperatorName.AND) {
-                        subs.addAll(((LogicalOperator<? super Feature>) sub).getOperands());
-                    } else {
-                        subs.add(sub);
-                    }
-                }
-                return ff.and(subs);
-            } else if (!exp.OR().isEmpty()) {
-                //| filter (OR filter)+
-                final List<Filter<? super Feature>> subs = new ArrayList<>();
-                for (FilterContext f : exp.filter()) {
-                    final Filter<? super Feature> sub = convertFilter(f, ff);
-                    if (sub.getOperatorType() == LogicalOperatorName.OR) {
-                        subs.addAll(((LogicalOperator<? super Feature>) sub).getOperands());
-                    } else {
-                        subs.add(sub);
-                    }
-                }
-                return ff.or(subs);
-            } else if (exp.LPAREN() != null) {
-                //| LPAREN filter RPAREN
-                return convertFilter(exp.filter(0), ff);
-            }
-        } else if (tree instanceof FilterTermContext) {
-            //: expression
-            //    (
-            //              COMPARE  expression
-            //            | NOT? IN LPAREN (expressionFctParam )?  RPAREN
-            //            | BETWEEN expression AND expression
-            //            | NOT? LIKE expression
-            //            | NOT? ILIKE expression
-            //            | IS NOT? NULL
-            //            | AFTER expression
-            //            | ANYINTERACTS expression
-            //            | BEFORE expression
-            //            | BEGINS expression
-            //            | BEGUNBY expression
-            //            | DURING expression
-            //            | ENDEDBY expression
-            //            | ENDS expression
-            //            | MEETS expression
-            //            | METBY expression
-            //            | OVERLAPPEDBY expression
-            //            | TCONTAINS expression
-            //            | TEQUALS expression
-            //            | TOVERLAPS expression
-            //    )
-            //| filterGeometry
-
-            final FilterTermContext exp = (FilterTermContext) tree;
-            final List<ExpressionContext> exps = exp.expression();
-            if (exp.COMPARE() != null) {
-                // expression COMPARE expression
-                final String text = exp.COMPARE().getText();
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                if ("=".equals(text)) {
-                    return ff.equal(left, right);
-                } else if ("<>".equals(text)) {
-                    return ff.notEqual(left, right);
-                } else if (">".equals(text)) {
-                    return ff.greater(left, right);
-                } else if ("<".equals(text)) {
-                    return ff.less(left, right);
-                } else if (">=".equals(text)) {
-                    return ff.greaterOrEqual(left, right);
-                } else if ("<=".equals(text)) {
-                    return ff.lessOrEqual(left, right);
-                }
-            } else if (exp.IN() != null) {
-                // expression NOT? IN LPAREN (expressionFctParam )?  RPAREN
-                final Expression<Feature,?> val = convertExpression(exps.get(0), ff);
-                final ExpressionFctParamContext prm = exp.expressionFctParam();
-                final List<ExpressionContext> params = prm.expression();
-                final List<Expression<Feature,?>> subexps = new ArrayList<>();
-                for (int i = 0, n = params.size(); i < n; i++) {
-                    subexps.add(convertExpression(params.get(i), ff));
-                }
-                final int size = subexps.size();
-                final Filter<Feature> selection;
-                switch (size) {
-                    case 0: {
-                        selection = Filter.exclude();
-                        break;
-                    }
-                    case 1: {
-                        selection = ff.equal(val, subexps.get(0));
-                        break;
-                    }
-                    default: {
-                        final List<Filter<? super Feature>> filters = new ArrayList<>();
-                        for (Expression<Feature,?> e : subexps) {
-                            filters.add(ff.equal(val, e));
-                        }   selection = ff.or(filters);
-                        break;
-                    }
-                }
-                if (exp.NOT() != null) {
-                    return ff.not(selection);
-                } else {
-                    return selection;
-                }
-            } else if (exp.BETWEEN() != null) {
-                // expression BETWEEN expression AND expression
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                final Expression<Feature,?> exp3 = convertExpression(exps.get(2), ff);
-                return ff.between(exp1, exp2, exp3);
-            } else if (exp.LIKE() != null) {
-                // expression NOT? LIKE expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                if (exp.NOT() != null) {
-                    return ff.not(ff.like(left, right.apply(null).toString(), '%', '_', '\\', true));
-                } else {
-                    return ff.like(left, right.apply(null).toString(), '%', '_', '\\', true);
-                }
-            } else if (exp.ILIKE() != null) {
-                // expression NOT? LIKE expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                if (exp.NOT() != null) {
-                    return ff.not(ff.like(left, right.apply(null).toString(), '%', '_', '\\', false));
-                } else {
-                    return ff.like(left, right.apply(null).toString(), '%', '_', '\\', false);
-                }
-            } else if (exp.IS() != null) {
-                // expression IS NOT? NULL
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                if (exp.NOT() != null) {
-                    return ff.not(ff.isNull(exp1));
-                } else {
-                    return ff.isNull(exp1);
-                }
-            } else if (exp.AFTER() != null) {
-                // expression AFTER expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.after(left, right);
-            } else if (exp.ANYINTERACTS() != null) {
-                // expression ANYINTERACTS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.anyInteracts(left, right);
-            } else if (exp.BEFORE() != null) {
-                // expression BEFORE expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.before(left, right);
-            } else if (exp.BEGINS() != null) {
-                // expression BEGINS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.begins(left, right);
-            } else if (exp.BEGUNBY() != null) {
-                // expression BEGUNBY expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.begunBy(left, right);
-            } else if (exp.DURING() != null) {
-                // expression DURING expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.during(left, right);
-            } else if (exp.ENDEDBY() != null) {
-                // expression ENDEDBY expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.endedBy(left, right);
-            } else if (exp.ENDS() != null) {
-                // expression ENDS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.ends(left, right);
-            } else if (exp.MEETS() != null) {
-                // expression MEETS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.meets(left, right);
-            } else if (exp.METBY() != null) {
-                // expression METBY expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.metBy(left, right);
-            } else if (exp.OVERLAPPEDBY() != null) {
-                // expression OVERLAPPEDBY expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.overlappedBy(left, right);
-            } else if (exp.TCONTAINS() != null) {
-                // expression TCONTAINS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.tcontains(left, right);
-            } else if (exp.TEQUALS() != null) {
-                // expression TEQUALS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.tequals(left, right);
-            } else if (exp.TOVERLAPS() != null) {
-                // expression TOVERLAPS expression
-                final Expression<Feature,?> left  = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
-                return ff.toverlaps(left, right);
-            } else if (exp.filterGeometry() != null) {
-                // expression filterGeometry
-                return convertFilter(exp.filterGeometry(), ff);
-            }
-        } else if (tree instanceof FilterGeometryContext) {
-            //: BBOX LPAREN (PROPERTY_NAME|NAME) COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary (COMMA TEXT)? RPAREN
-            //| BEYOND LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-            //| CONTAINS LPAREN expression COMMA expression RPAREN
-            //| CROSSES LPAREN expression COMMA expression RPAREN
-            //| DISJOINT LPAREN expression COMMA expression RPAREN
-            //| DWITHIN LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-            //| EQUALS LPAREN expression COMMA expression RPAREN
-            //| INTERSECTS LPAREN expression COMMA expression RPAREN
-            //| OVERLAPS LPAREN expression COMMA expression RPAREN
-            //| TOUCHES LPAREN expression COMMA expression RPAREN
-            //| WITHIN LPAREN expression COMMA expression RPAREN
-
-            final FilterGeometryContext exp = (FilterGeometryContext) tree;
-            final List<ExpressionContext> exps = exp.expression();
-            if (exp.BBOX() != null) {
-                final Expression<Feature,?> prop = convertExpression(exps.get(0), ff);
-                final double v1 = unaryAsNumber(exp.expressionUnary(0)).doubleValue();
-                final double v2 = unaryAsNumber(exp.expressionUnary(1)).doubleValue();
-                final double v3 = unaryAsNumber(exp.expressionUnary(2)).doubleValue();
-                final double v4 = unaryAsNumber(exp.expressionUnary(3)).doubleValue();
-                GeneralEnvelope env;
-                if (exp.TEXT() != null) try {
-                    String crs = convertExpression(exp.TEXT(), ff).apply(null).toString();
-                    env = new GeneralEnvelope(CRS.forCode(crs));
-                } catch (FactoryException e) {
-                    throw new CQLException("Can not parse CRS code.", e);
-                } else {
-                    env = new GeneralEnvelope(2);
-                }
-                env.setRange(0, v1, v3);
-                env.setRange(1, v2, v4);
-                return ff.bbox(prop, env);
-            } else if (exp.BEYOND() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                final double distance = ((Number) convertExpression(exps.get(2), ff).apply(null)).doubleValue();
-                final Unit<Length> unit = parseLengthUnit(convertExpression(exps.get(3), ff));
-                return ff.beyond(exp1, exp2, Quantities.create(distance, unit));
-            } else if (exp.CONTAINS() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.contains(exp1, exp2);
-            } else if (exp.CROSSES() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.crosses(exp1, exp2);
-            } else if (exp.DISJOINT() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.disjoint(exp1, exp2);
-            } else if (exp.DWITHIN() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                final double distance = ((Number) convertExpression(exps.get(2), ff).apply(null)).doubleValue();
-                final Unit<Length> unit = parseLengthUnit(convertExpression(exps.get(3), ff));
-                return ff.within(exp1, exp2, Quantities.create(distance, unit));
-            } else if (exp.EQUALS() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.equals(exp1, exp2);
-            } else if (exp.INTERSECTS() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.intersects(exp1, exp2);
-            } else if (exp.OVERLAPS() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.overlaps(exp1, exp2);
-            } else if (exp.TOUCHES() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.touches(exp1, exp2);
-            } else if (exp.WITHIN() != null) {
-                final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
-                final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
-                return ff.within(exp1, exp2);
-            }
-        }
-        throw new CQLException("Unreconized filter : type=" + tree.getText());
-    }
-
-    /**
-     * Convert the given tree in a Query.
-     */
-    private static Query convertQuery(ParseTree tree, FilterFactory<Feature,Object,Object> ff) throws CQLException {
-        if (tree instanceof QueryContext) {
-            final QueryContext context = (QueryContext) tree;
-            final List<ProjectionContext> projections = context.projection();
-            final WhereContext where = context.where();
-            final OffsetContext offset = context.offset();
-            final LimitContext limit = context.limit();
-            final OrderbyContext orderby = context.orderby();
-
-            final Query query = new Query();
-            if (context.MULT() == null) {
-                for (ProjectionContext pc : projections) {
-                    final Expression<Feature, ?> exp = convertExpression(pc.expression(), ff);
-                    if (pc.AS() != null) {
-                        final Expression<Feature, ?> alias = convertExpression(pc.TEXT(), ff);
-                        query.projections.add(new Query.Projection(exp, String.valueOf(( (Literal) alias).getValue())));
-                    } else {
-                        query.projections.add(new Query.Projection(exp, null));
-                    }
-                }
-            }
-            if (where != null) {
-                query.filter = (Filter<Feature>) convertFilter(where.filter(), ff);
-            }
-            if (offset != null) {
-                query.offset = Integer.valueOf(offset.INT().getText());
-            }
-            if (limit != null) {
-                query.limit = Integer.valueOf(limit.INT().getText());
-            }
-            if (orderby != null) {
-                for (SortpropContext spc : orderby.sortprop()) {
-                    final Expression<Feature, ?> exp = convertExpression(spc.expression(), ff);
-                    if (exp instanceof ValueReference) {
-                        query.sortby.add(ff.sort((ValueReference<? super Feature, ?>) exp,
-                                spc.DESC() != null ? SortOrder.DESCENDING : SortOrder.ASCENDING));
-                    } else {
-                        throw new CQLException("Sort by may be used with property names only");
-                    }
-                }
-            }
-            return query;
-        }
-        return null;
-    }
-}
diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/CQLException.java b/core/sis-cql/src/main/java/org/apache/sis/cql/CQLException.java
deleted file mode 100644
index 88fc422..0000000
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/CQLException.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.cql;
-
-
-/**
- * Thrown when a CQL statement can not be parsed.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final class CQLException extends Exception {
-    /**
-     * Serial number for inter-operability with different versions.
-     */
-    private static final long serialVersionUID = -1494977914551289727L;
-
-    /**
-     * Constructs an exception with the specified detail message.
-     *
-     * @param  message  the detail message.
-     */
-    public CQLException(String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs an exception with the specified detail message and cause.
-     *
-     * @param  message  the detail message.
-     * @param  cause    the cause for this exception.
-     */
-    public CQLException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java b/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java
deleted file mode 100644
index b71fef6..0000000
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * 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.cql;
-
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Pattern;
-import java.time.temporal.TemporalAccessor;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import javax.measure.Unit;
-import javax.measure.Quantity;
-import org.opengis.util.CodeList;
-import org.apache.sis.measure.UnitFormat;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.internal.feature.Geometries;
-import org.apache.sis.internal.feature.GeometryWrapper;
-import org.apache.sis.internal.filter.FunctionNames;
-import org.apache.sis.internal.filter.Visitor;
-import org.apache.sis.internal.util.StandardDateFormat;
-
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.DistanceOperatorName;
-import org.opengis.filter.TemporalOperatorName;
-import org.opengis.filter.BinarySpatialOperator;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.BetweenComparisonOperator;
-import org.opengis.filter.LikeOperator;
-import org.opengis.filter.Expression;
-
-// Optional dependencies
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.io.WKTWriter;
-
-
-/**
- * Visitor to convert a Filter in CQL.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
-
-    static final FilterToCQLVisitor INSTANCE = new FilterToCQLVisitor();
-
-    /**
-     * Pattern to check for property name to escape against regExp
-     */
-    private final Pattern patternPropertyName;
-
-    /**
-     * Formatter to use for unit symbol. Not thread-safe; usage must be synchronized.
-     */
-    private final UnitFormat unitFormat;
-
-    /**
-     * Creates a new visitor.
-     */
-    private FilterToCQLVisitor() {
-        patternPropertyName = Pattern.compile("[,+\\-/*\\t\\n\\r\\d\\s]");
-        unitFormat = new UnitFormat(Locale.US);
-        unitFormat.setStyle(UnitFormat.Style.NAME);
-
-        constant(Filter.exclude(), "1=0");
-        constant(Filter.include(), "1=1");
-        operatorBetweenValues(LogicalOperatorName.AND, "AND");
-        operatorBetweenValues(LogicalOperatorName.OR,  "OR");
-        setFilterHandler(LogicalOperatorName.NOT, (f,sb) -> {
-            final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) f;
-            format(sb.append("NOT "), filter.getOperands().get(0));
-        });
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN), (f,sb) -> {
-            final BetweenComparisonOperator<Feature> filter = (BetweenComparisonOperator<Feature>) f;
-            format(sb, filter.getExpression());
-            format(sb.append(" BETWEEN "), filter.getLowerBoundary());
-            format(sb.append(" AND "), filter.getUpperBoundary());
-        });
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO,                 "=");
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_NOT_EQUAL_TO,             "<>");
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN,             ">");
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO, ">=");
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_LESS_THAN,                "<");
-        operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,    "<=");
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE), (f,sb) -> {
-            final LikeOperator<Feature> filter = (LikeOperator<Feature>) f;
-            List<Expression<? super Feature, ?>> operands = f.getExpressions();
-            format(sb, operands.get(0));
-            // TODO: ILIKE is not standard SQL.
-            sb.append(filter.isMatchingCase() ? " LIKE " : " ILIKE ");
-            // TODO: convert wildcards and escape to SQL 92.
-            format(sb, operands.get(1));
-        });
-        operatorAfterValue(FunctionNames.PROPERTY_IS_NULL, " IS NULL");
-        operatorAfterValue(FunctionNames.PROPERTY_IS_NIL,  " IS NIL");
-        /*
-         * Spatial filters.
-         */
-        setFilterHandler(SpatialOperatorName.BBOX, (f,sb) -> {
-            final BinarySpatialOperator<Feature> filter = (BinarySpatialOperator<Feature>) f;
-            final Expression<? super Feature, ?> left  = filter.getOperand1();
-            final Expression<? super Feature, ?> right = filter.getOperand2();
-            final ValueReference<? super Feature, ?> pName =
-                    (left  instanceof ValueReference) ? (ValueReference<? super Feature, ?>) left :
-                    (right instanceof ValueReference) ? (ValueReference<? super Feature, ?>) right : null;
-            final Object lit = ((left instanceof Literal)
-                    ? (Literal<? super Feature, ?>) left
-                    : (Literal<? super Feature, ?>) right).getValue();      // TODO: potential classCastException.
-
-            final GeneralEnvelope e = Geometries.wrap(lit).map(GeometryWrapper::getEnvelope).orElse(null);
-            if (e != null) {
-                if (e.getDimension() > 2) {
-                    throw new UnsupportedOperationException("Only 2D envelopes accepted");
-                }
-                sb.append("BBOX(").append(pName.getXPath()).append(", ");
-                sb.append(e.getMinimum(0)).append(", ")
-                  .append(e.getMaximum(0)).append(", ")
-                  .append(e.getMinimum(1)).append(", ")
-                  .append(e.getMaximum(1)).append(')');
-            } else {
-                // Use writing BBOX(exp1,exp2).
-                format(sb.append("BBOX("), left);
-                format(sb.append(','), right);
-                sb.append(')');
-            }
-        });
-        for (final SpatialOperatorName type : SpatialOperatorName.values()) {
-            if (type != SpatialOperatorName.BBOX) {
-                function(type, type.identifier().toUpperCase(Locale.US));
-                if (type == SpatialOperatorName.OVERLAPS) break;
-            }
-        }
-        function(DistanceOperatorName.WITHIN, "DWITHIN");
-        function(DistanceOperatorName.BEYOND, "BEYOND");
-        for (final TemporalOperatorName type : TemporalOperatorName.values()) {
-            function(type, type.identifier().toUpperCase(Locale.US));
-            if (type == TemporalOperatorName.ANY_INTERACTS) break;
-        }
-        /*
-         * Expressions
-         */
-        setExpressionHandler(FunctionNames.Literal, (e,sb) -> {
-            final Literal<Feature,?> exp = (Literal<Feature,?>) e;
-            final Object value = exp.getValue();
-            if (value instanceof Quantity<?>) {
-                final Quantity<?> q = (Quantity<?>) value;
-                final Unit<?> unit = q.getUnit();
-                sb.append(q.getValue().doubleValue()).append(", '");
-                try {
-                    synchronized (unitFormat) {
-                        unitFormat.format(unit, sb);
-                    }
-                } catch (IOException ex) {
-                    throw new UncheckedIOException(ex);     // Should never happen.
-                }
-                sb.append('\'');
-            } else if (value instanceof Number) {
-                sb.append(value);
-            } else if (value instanceof Date) {
-                final Date date = (Date) value;
-                sb.append(StandardDateFormat.FORMAT.format(date.toInstant()));
-            } else if (value instanceof TemporalAccessor) {
-                final TemporalAccessor date = (TemporalAccessor) value;
-                sb.append(StandardDateFormat.FORMAT.format(date));
-            } else if (value instanceof Geometry) {
-                final Geometry geometry = (Geometry) value;
-                final WKTWriter writer = new WKTWriter();
-                sb.append(writer.write(geometry));
-            } else {
-                sb.append('\'').append(value).append('\'');
-            }
-        });
-        setExpressionHandler(FunctionNames.ValueReference, (e,sb) -> {
-            final ValueReference<Feature,?> exp = (ValueReference<Feature,?>) e;
-            final String name = exp.getXPath();
-            if (patternPropertyName.matcher(name).find()) {
-                // Escape for special chars
-                sb.append('"').append(name).append('"');
-            } else {
-                sb.append(name);
-            }
-        });
-        arithmetic(FunctionNames.Add,      '+');
-        arithmetic(FunctionNames.Divide,   '/');
-        arithmetic(FunctionNames.Multiply, '*');
-        arithmetic(FunctionNames.Subtract, '-');
-    }
-
-    private void constant(final Filter<?> type, final String text) {
-        setFilterHandler(type.getOperatorType(), (f,sb) -> sb.append(text));
-    }
-
-    private void operatorAfterValue(final String type, final String operator) {
-        setFilterHandler(ComparisonOperatorName.valueOf(type), (f,sb) -> {
-            format(sb, f.getExpressions().get(0));
-            sb.append(operator);
-        });
-    }
-
-    private void operatorBetweenValues(final ComparisonOperatorName type, final String operator) {
-        setFilterHandler(type, (f,sb) -> {
-            final List<Expression<? super Feature, ?>> operands = f.getExpressions();
-            format(sb, operands.get(0));
-            final int n = operands.size();
-            for (int i=1; i<n; i++) {
-                // Should execute only once. If n>2, make the problem visible in the CQL.
-                format(sb.append(' ').append(operator).append(' '), operands.get(i));
-            }
-        });
-    }
-
-    private void operatorBetweenValues(final LogicalOperatorName type, final String operator) {
-        setFilterHandler(type, (f,sb) -> {
-            final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) f;
-            final List<Filter<? super Feature>> operands = filter.getOperands();
-            format(sb.append('('), operands.get(0));
-            final int n = operands.size();
-            for (int i=1; i<n; i++) {
-                format(sb.append(' ').append(operator).append(' '), operands.get(i));
-            }
-            sb.append(')');
-        });
-    }
-
-    private void function(final CodeList<?> type, final String operator) {
-        setFilterHandler(type, (f,sb) -> {
-            final List<Expression<? super Feature, ?>> operands = f.getExpressions();
-            sb.append(operator).append('(');
-            final int n = operands.size();
-            for (int i=0; i<n; i++) {
-                if (i != 0) sb.append(", ");
-                format(sb, operands.get(i));
-            }
-            sb.append(')');
-        });
-    }
-
-    private void arithmetic(final String type, final char operator) {
-        setExpressionHandler(type, (e,sb) -> {
-            final List<Expression<? super Feature, ?>> parameters = e.getParameters();
-            format(sb, parameters.get(0));
-            final int n = parameters.size();
-            for (int i=1; i<n; i++) {
-                format(sb.append(' ').append(operator).append(' '), parameters.get(i));
-            }
-        });
-    }
-
-    /**
-     * Executes the registered action for the given filter.
-     *
-     * <h4>Note on type safety</h4>
-     * This method signature uses {@code <? super R>} for caller's convenience because this is the type that
-     * we get from {@link LogicalOperator#getOperands()}. But the {@link BiConsumer} uses exactly {@code <R>}
-     * type because doing otherwise causes complications with types that can not be expressed in Java (kinds
-     * of {@code <? super ? super R>}). The cast in this method is okay if we do not invoke any {@code filter}
-     * method with a return value (directly or indirectly as list elements) of exactly {@code <R>} type.
-     * Such methods do not exist in the GeoAPI interfaces, so we are safe if the {@link BiConsumer}
-     * does not invoke implementation-specific methods.
-     *
-     * @param  sb      where to write the result of all actions.
-     * @param  filter  the filter for which to execute an action based on its type.
-     * @throws UnsupportedOperationException if there is no action registered for the given filter.
-     */
-    @SuppressWarnings("unchecked")
-    private void format(final StringBuilder sb, final Filter<? super Feature> filter) {
-        visit((Filter<Feature>) filter, sb);
-    }
-
-    /**
-     * Executes the registered action for the given expression.
-     * Throws an exception if the expression did not write anything in the buffer.
-     *
-     * <h4>Note on type safety</h4>
-     * This method signature uses {@code <? super R>} for caller's convenience because this is the type that
-     * we get from {@link Expression#getParameters()}. But the {@link BiConsumer} expects exactly {@code <R>}
-     * type because doing otherwise causes complications with types that can not be expressed in Java (kinds
-     * of {@code <? super ? super R>}). The cast in this method is okay if we do not invoke any {@code exp}
-     * method with a return value (directly or indirectly as list elements) of exactly {@code <R>} type.
-     * Such methods do not exist in the GeoAPI interfaces, so we are safe if the {@link BiConsumer}
-     * does not invoke implementation-specific methods.
-     *
-     * @param  sb   where to write the result of all actions.
-     * @param  exp  the expression for which to execute an action based on its type.
-     * @throws UnsupportedOperationException if there is no action registered for the given expression.
-     */
-    @SuppressWarnings("unchecked")
-    private void format(final StringBuilder sb, final Expression<? super Feature, ?> expression) {
-        visit((Expression<Feature,?>) expression, sb);
-    }
-
-    @Override
-    protected void typeNotFound(final String type, final Expression<Feature,?> e, final StringBuilder sb) {
-        final List<Expression<? super Feature,?>> exps = e.getParameters();
-        sb.append(type).append('(');
-        final int n = exps.size();
-        for (int i=0; i<n; i++) {
-            if (i != 0) sb.append(", ");
-            format(sb, exps.get(i));
-        }
-        sb.append(')');
-    }
-}
diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/Query.java b/core/sis-cql/src/main/java/org/apache/sis/cql/Query.java
deleted file mode 100644
index 419edbc..0000000
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/Query.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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.cql;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Filter;
-import org.opengis.filter.SortProperty;
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class Query {
-
-    public final List<Projection> projections = new ArrayList<>();
-    public Filter<Feature> filter;
-    public Integer offset;
-    public Integer limit;
-    public final List<SortProperty> sortby = new ArrayList<>();
-
-    public Query() {
-    }
-
-    public Query(List<Projection> projections, Filter filter, List<SortProperty> sortby, Integer offset, Integer limit) {
-        if (projections != null) this.projections.addAll(projections);
-        this.filter = filter;
-        if (sortby != null) this.sortby.addAll(sortby);
-        this.offset = offset;
-        this.limit = limit;
-    }
-
-    @Override
-    public int hashCode() {
-        int hash = 3;
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final Query other = (Query) obj;
-        if (!Objects.equals(this.projections, other.projections)) {
-            return false;
-        }
-        if (!Objects.equals(this.filter, other.filter)) {
-            return false;
-        }
-        if (!Objects.equals(this.sortby, other.sortby)) {
-            return false;
-        }
-        if (!Objects.equals(this.offset, other.offset)) {
-            return false;
-        }
-        if (!Objects.equals(this.limit, other.limit)) {
-            return false;
-        }
-        return true;
-    }
-
-    public static class Projection {
-        public Expression<Feature, ?> expression;
-        public String alias;
-
-        public Projection(Expression<Feature, ?> expression, String alias) {
-            this.expression = expression;
-            this.alias = alias;
-        }
-
-        @Override
-        public int hashCode() {
-            int hash = 7;
-            hash = 19 * hash + Objects.hashCode(this.expression);
-            hash = 19 * hash + Objects.hashCode(this.alias);
-            return hash;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null) {
-                return false;
-            }
-            if (getClass() != obj.getClass()) {
-                return false;
-            }
-            final Projection other = (Projection) obj;
-            if (!Objects.equals(this.alias, other.alias)) {
-                return false;
-            }
-            if (!Objects.equals(this.expression, other.expression)) {
-                return false;
-            }
-            return true;
-        }
-
-    }
-}
diff --git a/core/sis-cql/src/main/java/org/apache/sis/internal/cql/AntlrCQL.java b/core/sis-cql/src/main/java/org/apache/sis/internal/cql/AntlrCQL.java
deleted file mode 100644
index 354915a..0000000
--- a/core/sis-cql/src/main/java/org/apache/sis/internal/cql/AntlrCQL.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.cql;
-
-import org.antlr.v4.runtime.CharStreams;
-import org.antlr.v4.runtime.CodePointCharStream;
-import org.antlr.v4.runtime.CommonTokenStream;
-import org.antlr.v4.runtime.RecognitionException;
-import org.antlr.v4.runtime.TokenStream;
-import org.antlr.v4.runtime.tree.ParseTree;
-
-
-/**
- * ANTLR CQL parser methods.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final class AntlrCQL {
-
-    private AntlrCQL() {
-    }
-
-    public static ParseTree compile(String cql) {
-        final Object obj = compileFilterOrExpression(cql);
-        ParseTree tree = null;
-        if (obj instanceof ParseTree) {
-            tree = (ParseTree) obj;
-        }
-        return tree;
-    }
-
-    public static Object compileExpression(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.ExpressionContext ctx = parser.expression();
-            return ctx;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-
-    public static Object compileFilter(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.FilterContext retfilter = parser.filter();
-
-            return retfilter;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-
-    public static Object compileFilterOrExpression(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.FilterOrExpressionContext retfilter = parser.filterOrExpression();
-
-            return retfilter;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-
-    public static Object compileQuery(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.QueryContext ctx = parser.query();
-            return ctx;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestCase.java b/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestCase.java
deleted file mode 100644
index d649f99..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestCase.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.cql;
-
-import org.opengis.filter.FilterFactory;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.test.TestCase;
-import org.opengis.feature.Feature;
-
-
-/**
- * Base class of all CQL tests.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-abstract strictfp class CQLTestCase extends TestCase {
-    /**
-     * The factory to use for creating filter and expressions.
-     */
-    final FilterFactory<Feature,Object,Object> FF;
-
-    /**
-     * The factory to use for creating Java Topology Suite (JTS) objects.
-     */
-    final GeometryFactory GF;
-
-    /**
-     * Creates a new test case.
-     */
-    CQLTestCase() {
-        FF = DefaultFilterFactory.forFeatures();
-        GF = org.apache.sis.internal.feature.jts.Factory.INSTANCE.factory(false);
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestSuite.java b/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestSuite.java
deleted file mode 100644
index 72d974d..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestSuite.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.cql;
-
-import org.apache.sis.test.TestSuite;
-import org.junit.BeforeClass;
-import org.junit.runners.Suite;
-
-
-/**
- * All tests from the {@code sis-cql} module.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-@Suite.SuiteClasses({
-    org.apache.sis.cql.ExpressionReadingTest.class,
-    org.apache.sis.cql.ExpressionWritingTest.class,
-    org.apache.sis.cql.FilterReadingTest.class,
-    org.apache.sis.cql.FilterWritingTest.class,
-    org.apache.sis.cql.QueryReadingTest.class,
-    org.apache.sis.cql.QueryWritingTest.class,
-})
-public final strictfp class CQLTestSuite extends TestSuite {
-    /**
-     * Verifies the list of tests before to run the suite.
-     * See {@link #verifyTestList(Class, Class[])} for more information.
-     */
-    @BeforeClass
-    public static void verifyTestList() {
-        assertNoMissingTest(CQLTestSuite.class);
-        verifyTestList(CQLTestSuite.class);
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionReadingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionReadingTest.java
deleted file mode 100644
index 51012f5..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionReadingTest.java
+++ /dev/null
@@ -1,609 +0,0 @@
-/*
- * 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.cql;
-
-import java.time.Instant;
-import java.text.ParseException;
-import java.time.Duration;
-import java.time.Period;
-import java.time.temporal.ChronoUnit;
-import java.time.temporal.TemporalAccessor;
-import java.time.temporal.TemporalUnit;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryCollection;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.MultiLineString;
-import org.locationtech.jts.geom.MultiPoint;
-import org.locationtech.jts.geom.MultiPolygon;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Test reading CQL expressions.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class ExpressionReadingTest extends CQLTestCase {
-    @Test
-    public void testValueReference1() throws CQLException {
-        final String cql = "geom";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof ValueReference);
-        final ValueReference expression = (ValueReference) obj;
-        assertEquals("geom", expression.getXPath());
-    }
-
-    @Test
-    public void testValueReference2() throws CQLException {
-        final String cql = "\"geom\"";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof ValueReference);
-        final ValueReference expression = (ValueReference) obj;
-        assertEquals("geom", expression.getXPath());
-    }
-
-    @Test
-    public void testValueReference3() throws CQLException {
-        final String cql = "ùth{e_$uglY^_pr@perté";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof ValueReference);
-        final ValueReference expression = (ValueReference) obj;
-        assertEquals("ùth{e_$uglY^_pr@perté", expression.getXPath());
-    }
-
-    @Test
-    public void testInteger() throws CQLException {
-        final String cql = "15";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Integer.valueOf(15), expression.getValue());
-    }
-
-    @Test
-    public void testNegativeInteger() throws CQLException {
-        final String cql = "-15";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Integer.valueOf(-15), expression.getValue());
-    }
-
-    @Test
-    public void testDecimal1() throws CQLException {
-        final String cql = "3.14";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Double.valueOf(3.14), expression.getValue());
-    }
-
-    @Test
-    public void testDecimal2() throws CQLException {
-        final String cql = "9e-1";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Double.valueOf(9e-1), expression.getValue());
-    }
-
-    @Test
-    public void testNegativeDecimal() throws CQLException {
-        final String cql = "-3.14";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Double.valueOf(-3.14), expression.getValue());
-    }
-
-    @Test
-    public void testText() throws CQLException {
-        final String cql = "'hello world'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals("hello world", expression.getValue());
-    }
-
-    @Test
-    public void testText2() throws CQLException {
-        final String cql = "'Valle d\\'Aosta'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals("Valle d'Aosta", expression.getValue());
-    }
-
-    @Test
-    public void testText3() throws CQLException {
-        final String cql = "'Valle d\\'Aosta/Vallée d\\'Aoste'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals("Valle d'Aosta/Vallée d'Aoste", expression.getValue());
-    }
-
-    @Test
-    public void testDate() throws CQLException, ParseException{
-        //dates are expected to be formated in ISO 8601 : yyyy-MM-dd'T'HH:mm:ss'Z'
-        final String cql = "2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        Object value = expression.getValue();
-        assertTrue(value instanceof TemporalAccessor);
-        TemporalAccessor acc = (TemporalAccessor) value;
-        assertEquals(Instant.parse("2012-03-21T05:42:36Z"), Instant.from(acc));
-    }
-
-    @Test
-    public void testDuration() throws CQLException, ParseException{
-        final String cql = "P7Y6M5D";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertTrue(expression.getValue() instanceof Period);
-        final Period period = (Period) expression.getValue();
-        assertEquals(7, period.getYears());
-        assertEquals(6, period.getMonths());
-        assertEquals(5, period.getDays());
-    }
-
-    @Test
-    public void testDuration2() throws CQLException, ParseException{
-        final String cql = "PT4H3M2S";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertTrue(expression.getValue() instanceof Duration);
-        final Duration duration = (Duration) expression.getValue();
-        assertEquals(14582, duration.get(ChronoUnit.SECONDS));
-    }
-
-    @Test
-    public void testAddition() throws CQLException {
-        final String cql = "3 + 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        assertEquals(FF.add(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Test
-    @Ignore("String cannot be cast to Number.")
-    public void testAddition2() throws CQLException {
-        final String cql = "'test' + '23'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        Object res = expression.apply(null);
-        assertEquals("test23", res);
-    }
-
-    @Test
-    public void testSubstract() throws CQLException {
-        final String cql = "3 - 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        assertEquals(FF.subtract(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Test
-    public void testMultiply() throws CQLException {
-        final String cql = "3 * 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        assertEquals(FF.multiply(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Test
-    public void testDivide() throws CQLException {
-        final String cql = "3 / 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        assertEquals(FF.divide(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Test
-    @Ignore("Function `max` not yet supported.")
-    public void testFunction1() throws CQLException {
-        final String cql = "max(\"att\",15)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression<Feature,?> expression = (Expression<Feature,?>) obj;
-        assertEquals(FF.function("max",FF.property("att"), FF.literal(15)), expression);
-    }
-
-    @Test
-    @Ignore("Function `min` not yet supported.")
-    public void testFunction2() throws CQLException {
-        final String cql = "min(\"att\",cos(3.14))";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression<Feature,?> expression = (Expression<Feature,?>) obj;
-        assertEquals(FF.function("min",FF.property("att"), FF.function("cos",FF.literal(3.14d))), expression);
-    }
-
-    @Test
-    public void testGeometryPoint() throws CQLException {
-        final String cql = "POINT(15 30)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createPoint(new Coordinate(15, 30));
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryPointEmpty() throws CQLException {
-        final String cql = "POINT EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof Point);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryMPoint() throws CQLException {
-        final String cql = "MULTIPOINT(15 30, 45 60)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createMultiPoint(
-                new Coordinate[]{
-                    new Coordinate(15, 30),
-                    new Coordinate(45, 60)
-                });
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryMPointEmpty() throws CQLException {
-        final String cql = "MULTIPOINT EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof MultiPoint);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryLineString() throws CQLException {
-        final String cql = "LINESTRING(10 20, 30 40, 50 60)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createLineString(
-                new Coordinate[]{
-                    new Coordinate(10, 20),
-                    new Coordinate(30, 40),
-                    new Coordinate(50, 60)
-                });
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryLineStringEmpty() throws CQLException {
-        final String cql = "LINESTRING EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof LineString);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryMLineString() throws CQLException {
-        final String cql = "MULTILINESTRING((10 20, 30 40, 50 60),(70 80, 90 100, 110 120))";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createMultiLineString(
-                new LineString[]{
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(10, 20),
-                            new Coordinate(30, 40),
-                            new Coordinate(50, 60)
-                        }),
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120)
-                        })
-                    }
-                );
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryMLineStringEmpty() throws CQLException {
-        final String cql = "MULTILINESTRING EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof MultiLineString);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryPolygon() throws CQLException {
-        final String cql = "POLYGON((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80))";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryPolygonEmpty() throws CQLException {
-        final String cql = "POLYGON EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof Polygon);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryMPolygon() throws CQLException {
-        final String cql = "MULTIPOLYGON("
-                + "((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80)),"
-                + "((11 21, 31 41, 51 61, 11 21), (71 81, 91 101, 111 121, 71 81))"
-                + ")";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Polygon geom1 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        final Polygon geom2 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(11, 21),
-                        new Coordinate(31, 41),
-                        new Coordinate(51, 61),
-                        new Coordinate(11, 21)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(71, 81),
-                            new Coordinate(91, 101),
-                            new Coordinate(111, 121),
-                            new Coordinate(71, 81)
-                        })
-                    }
-                );
-        final Geometry geom = GF.createMultiPolygon(new Polygon[]{geom1,geom2});
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryMPolygonEmpty() throws CQLException {
-        final String cql = "MULTIPOLYGON EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof MultiPolygon);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryCollection() throws CQLException {
-        final String cql = "GEOMETRYCOLLECTION( POINT(15 30), LINESTRING(10 20, 30 40, 50 60) )";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom1 =  GF.createPoint(new Coordinate(15, 30));
-        final Geometry geom2 =  GF.createLineString(
-                new Coordinate[]{
-                    new Coordinate(10, 20),
-                    new Coordinate(30, 40),
-                    new Coordinate(50, 60)
-                });
-        final GeometryCollection geom =  GF.createGeometryCollection(new Geometry[]{geom1,geom2});
-        final GeometryCollection returned = (GeometryCollection)expression.getValue();
-        assertEquals(geom.getNumGeometries(), returned.getNumGeometries());
-        assertEquals(geom.getGeometryN(0), returned.getGeometryN(0));
-        assertEquals(geom.getGeometryN(1), returned.getGeometryN(1));
-    }
-
-    @Test
-    public void testGeometryCollectionEmpty() throws CQLException {
-        final String cql = "GEOMETRYCOLLECTION EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof GeometryCollection);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryEnvelope() throws CQLException {
-        final String cql = "ENVELOPE(10, 20, 40, 30)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 40),
-                        new Coordinate(20, 40),
-                        new Coordinate(20, 30),
-                        new Coordinate(10, 30),
-                        new Coordinate(10, 40)
-                    }),
-                new LinearRing[0]
-                );
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryEnvelopeEmpty() throws CQLException {
-        final String cql = "ENVELOPE EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof Polygon);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testCombine1() throws CQLException {
-        final String cql = "((3*1)+(2-6))/4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression expression = (Expression) obj;
-        assertEquals(
-                FF.divide(
-                    FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.subtract(FF.literal(2), FF.literal(6))
-                        ),
-                    FF.literal(4))
-                , expression);
-    }
-
-    @Test
-    public void testCombine2() throws CQLException {
-        final String cql = "3*1+2/4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression rootAdd = (Expression) obj;
-        assertEquals(
-                    FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        )
-                , rootAdd);
-    }
-
-    @Test
-    @Ignore("Function `max` not yet supported.")
-    public void testCombine3() throws CQLException {
-        final String cql = "3*max(val,15)+2/4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression rootAdd = (Expression) obj;
-        assertEquals(
-                    FF.add(
-                        FF.multiply(
-                            FF.literal(3),
-                            FF.function("max", FF.property("val"), FF.literal(15)).toValueType(Number.class)
-                        ),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        )
-                , rootAdd);
-    }
-
-    @Test
-    @Ignore("Function `max` not yet supported.")
-    public void testCombine4() throws CQLException {
-        final String cql = "3 * max ( val , 15 ) + 2 / 4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression rootAdd = (Expression) obj;
-        assertEquals(
-                    FF.add(
-                        FF.multiply(
-                            FF.literal(3),
-                            FF.function("max", FF.property("val", Number.class), FF.literal(15)).toValueType(Number.class)
-                        ),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        )
-                , rootAdd);
-    }
-
-    @Test
-    @Ignore("Difference in the class argument of `property(…)`.")
-    public void testCombine5() throws CQLException {
-        final String cql = "(\"NB-Curistes\"*50)/12000";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Expression);
-        final Expression result = (Expression) obj;
-        assertEquals(
-                FF.divide(
-                        FF.multiply(
-                                FF.property("NB-Curistes", Number.class),
-                                FF.literal(50)
-                        ),
-                        FF.literal(12000)
-                )
-                , result);
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionWritingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionWritingTest.java
deleted file mode 100644
index 27cbc96..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionWritingTest.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * 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.cql;
-
-import java.time.Instant;
-import java.text.ParseException;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.filter.Expression;
-import org.opengis.feature.Feature;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Test writing in CQL expressions.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class ExpressionWritingTest extends CQLTestCase {
-    @Test
-    public void testValueReference1() throws CQLException {
-        final Expression<Feature,?> exp = FF.property("geom");
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("geom", cql);
-    }
-
-    @Test
-    public void testValueReference2() throws CQLException {
-        final Expression<Feature,?> exp = FF.property("the geom");
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("\"the geom\"", cql);
-    }
-
-    @Test
-    public void testInteger() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal(15);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("15", cql);
-    }
-
-    @Test
-    public void testNegativeInteger() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal(-15);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("-15", cql);
-    }
-
-    @Test
-    public void testDecimal1() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal(3.14);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3.14", cql);
-    }
-
-    @Test
-    public void testDecimal2() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal(9.0E-21);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("9.0E-21", cql);
-    }
-
-    @Test
-    @Ignore("Unsupported temporal field: Year")
-    public void testDate() throws CQLException, ParseException{
-        final Expression<Feature,?> exp = FF.literal(Instant.parse("2012-03-21T05:42:36Z"));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("2012-03-21T05:42:36Z", cql);
-    }
-
-
-    @Test
-    public void testNegativeDecimal() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal(-3.14);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("-3.14", cql);
-    }
-
-    @Test
-    public void testText() throws CQLException {
-        final Expression<Feature,?> exp = FF.literal("hello world");
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("'hello world'", cql);
-    }
-
-    @Test
-    public void testAdd() throws CQLException {
-        final Expression<Feature,?> exp = FF.add(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 + 2", cql);
-    }
-
-    @Test
-    public void testSubtract() throws CQLException {
-        final Expression<Feature,?> exp = FF.subtract(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 - 2", cql);
-    }
-
-    @Test
-    public void testMultiply() throws CQLException {
-        final Expression<Feature,?> exp = FF.multiply(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * 2", cql);
-    }
-
-    @Test
-    public void testDivide() throws CQLException {
-        final Expression<Feature,?> exp = FF.divide(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 / 2", cql);
-    }
-
-    @Test
-    @Ignore("Function `max` not yet supported.")
-    public void testFunction1() throws CQLException {
-        final Expression<Feature,?> exp = FF.function("max", FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("max(att , 15)", cql);
-    }
-
-    @Test
-    @Ignore("Function `min` not yet supported.")
-    public void testFunction2() throws CQLException {
-        final Expression<Feature,?> exp = FF.function("min",FF.property("att"), FF.function("cos", FF.literal(3.14d)));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("min(att , cos(3.14))", cql);
-    }
-
-    @Test
-    public void testCombine1() throws CQLException {
-        final Expression<Feature,?> exp =
-                FF.divide(
-                    FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.subtract(FF.literal(2), FF.literal(6))
-                        ),
-                    FF.literal(4));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * 1 + 2 - 6 / 4", cql);
-    }
-
-    @Test
-    public void testCombine2() throws CQLException {
-        final Expression<Feature,?> exp =
-                FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        );
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * 1 + 2 / 4", cql);
-
-    }
-
-    @Test
-    @Ignore("Function `max` not yet supported.")
-    public void testCombine3() throws CQLException {
-        final Expression<Feature,?> exp =
-                FF.add(
-                        FF.multiply(
-                            FF.literal(3),
-                            FF.function("max", FF.property("val"), FF.literal(15)).toValueType(Number.class)
-                        ),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        );
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * max(val , 15) + 2 / 4", cql);
-    }
-
-    @Test
-    public void testPoint() throws CQLException {
-        final Geometry geom = GF.createPoint(new Coordinate(15, 30));
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("POINT (15 30)", cql);
-    }
-
-    @Test
-    public void testMPoint() throws CQLException {
-        final Geometry geom = GF.createMultiPoint(
-                new Coordinate[]{
-                    new Coordinate(15, 30),
-                    new Coordinate(45, 60)
-                });
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("MULTIPOINT ((15 30), (45 60))", cql);
-    }
-
-    @Test
-    public void testLineString() throws CQLException {
-        final Geometry geom = GF.createLineString(
-                new Coordinate[]{
-                    new Coordinate(10, 20),
-                    new Coordinate(30, 40),
-                    new Coordinate(50, 60)
-                });
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("LINESTRING (10 20, 30 40, 50 60)", cql);
-    }
-
-    @Test
-    public void testMLineString() throws CQLException {
-        final Geometry geom = GF.createMultiLineString(
-                new LineString[]{
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(10, 20),
-                            new Coordinate(30, 40),
-                            new Coordinate(50, 60)
-                        }),
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120)
-                        })
-                    }
-                );
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("MULTILINESTRING ((10 20, 30 40, 50 60), (70 80, 90 100, 110 120))", cql);
-    }
-
-    @Test
-    public void testPolygon() throws CQLException {
-        final Geometry geom = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("POLYGON ((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80))", cql);
-    }
-
-    @Test
-    public void testMPolygon() throws CQLException {
-        final Polygon geom1 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        final Polygon geom2 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(11, 21),
-                        new Coordinate(31, 41),
-                        new Coordinate(51, 61),
-                        new Coordinate(11, 21)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(71, 81),
-                            new Coordinate(91, 101),
-                            new Coordinate(111, 121),
-                            new Coordinate(71, 81)
-                        })
-                    }
-                );
-        final Geometry geom = GF.createMultiPolygon(new Polygon[]{geom1,geom2});
-        final Expression<Feature,?> exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("MULTIPOLYGON (((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80)), ((11 21, 31 41, 51 61, 11 21), (71 81, 91 101, 111 121, 71 81)))", cql);
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterReadingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/FilterReadingTest.java
deleted file mode 100644
index 29d7fd8..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterReadingTest.java
+++ /dev/null
@@ -1,450 +0,0 @@
-/*
- * 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.cql;
-
-import java.util.List;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Iterator;
-import java.time.Instant;
-import java.time.temporal.TemporalAccessor;
-import java.text.ParseException;
-import javax.measure.Quantity;
-import javax.measure.quantity.Length;
-
-import org.opengis.geometry.Envelope;
-import org.opengis.util.CodeList;
-
-import org.apache.sis.measure.Units;
-import org.apache.sis.measure.Quantities;
-import org.apache.sis.geometry.AbstractEnvelope;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.geometry.Envelope2D;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.internal.util.UnmodifiableArrayList;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.opengis.test.Assert.*;
-
-// Optional dependencies
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LinearRing;
-
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.*;
-
-
-/**
- * Test reading CQL filters.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class FilterReadingTest extends CQLTestCase {
-
-    private static final double DELTA = 0.00000001;
-
-    private final Geometry baseGeometry = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[0]
-                );
-
-    private final Geometry baseGeometryPoint = GF.createPoint(
-                new Coordinate(12.1, 28.9));
-
-    @Test
-    public void testNullFilter() throws CQLException {
-        //this is not true cql but is since in commun use cases.
-        String cql = "";
-        Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(Filter.include(), filter);
-
-        cql = "*";
-        filter = CQL.parseFilter(cql);
-        assertEquals(Filter.include(), filter);
-    }
-
-    @Test
-    public void testAnd() throws CQLException {
-        final String cql = "att1 = 15 AND att2 = 30 AND att3 = 50";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(
-                FF.and(
-                UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
-                    FF.equal(FF.property("att1"), FF.literal(15)),
-                    FF.equal(FF.property("att2"), FF.literal(30)),
-                    FF.equal(FF.property("att3"), FF.literal(50))
-                })),
-                filter);
-    }
-
-    @Test
-    public void testOr() throws CQLException {
-        final String cql = "att1 = 15 OR att2 = 30 OR att3 = 50";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(
-                FF.or(
-                UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
-                    FF.equal(FF.property("att1"), FF.literal(15)),
-                    FF.equal(FF.property("att2"), FF.literal(30)),
-                    FF.equal(FF.property("att3"), FF.literal(50))
-                })),
-                filter);
-    }
-
-    @Test
-    public void testOrAnd1() throws CQLException {
-        final String cql = "Title = 'VMAI' OR (Title ILIKE '!$Pa_tt%ern?' AND DWITHIN(BoundingBox, POINT(12.1 28.9), 10, 'meter'))";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(
-                FF.or(
-                    FF.equal(FF.property("Title"), FF.literal("VMAI")),
-                    FF.and(
-                        FF.like(FF.property("Title"), "!$Pa_tt%ern?", '%', '_', '\\', false),
-                        FF.within(FF.property("BoundingBox"), FF.literal(baseGeometryPoint), Quantities.create(10, Units.METRE))
-                        )
-                ),
-                filter);
-    }
-
-    @Test
-    public void testOrAnd2() throws CQLException {
-        final Geometry geom =  GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 40),
-                        new Coordinate(20, 40),
-                        new Coordinate(20, 30),
-                        new Coordinate(10, 30),
-                        new Coordinate(10, 40)
-                    }),
-                new LinearRing[0]
-                );
-
-        final String cql = "NOT (INTERSECTS(BoundingBox, ENVELOPE(10, 20, 40, 30)) OR CONTAINS(BoundingBox, POINT(12.1 28.9))) AND BBOX(BoundingBox, 10,20,30,40)";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(
-                FF.and(
-                    FF.not(
-                        FF.or(
-                            FF.intersects(FF.property("BoundingBox"), FF.literal(geom)),
-                            FF.contains(FF.property("BoundingBox"), FF.literal(baseGeometryPoint))
-                            )
-                    ),
-                    FF.bbox(FF.property("BoundingBox"), new GeneralEnvelope(new Envelope2D(null, 10, 20, 30-10, 40-20)))
-                ),
-                filter);
-    }
-
-    @Test
-    public void testNot() throws CQLException {
-        final String cql = "NOT att = 15";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(FF.not(FF.equal(FF.property("att"), FF.literal(15))), filter);
-    }
-
-    @Test
-    public void testPropertyIsBetween() throws CQLException {
-        final String cql = "att BETWEEN 15 AND 30";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertEquals(FF.between(FF.property("att"), FF.literal(15), FF.literal(30)), filter);
-    }
-
-    @Test
-    public void testIn() throws CQLException {
-        verifyIn((LogicalOperator<?>) CQL.parseFilter("att IN ( 15, 30, 'hello')"));
-    }
-
-    @Test
-    public void testNotIn() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att NOT IN ( 15, 30, 'hello')");
-        assertInstanceOf("NOT", LogicalOperator.class, filter);
-        assertEquals(LogicalOperatorName.NOT, filter.getOperatorType());
-        verifyIn((LogicalOperator<?>) ((LogicalOperator<?>) filter).getOperands().get(0));
-    }
-
-    private void verifyIn(final LogicalOperator<?> filter) {
-        assertInstanceOf("OR", LogicalOperator.class, filter);
-        assertEquals(LogicalOperatorName.OR, filter.getOperatorType());
-        final Iterator<?> expected = Arrays.asList(15, 30, "hello").iterator();
-        for (final Filter<?> operand : filter.getOperands()) {
-            assertEquals(FF.equal(FF.property("att"), FF.literal(expected.next())), operand);
-        }
-        assertFalse(expected.hasNext());
-    }
-
-    @Test
-    public void testPropertyIsEqualTo1() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att=15");
-        assertEquals(FF.equal(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsEqualTo2() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att = 15");
-        assertEquals(FF.equal(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsNotEqualTo() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att <> 15");
-        assertEquals(FF.notEqual(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsNotEqualTo2() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att <>'15'");
-        assertEquals(FF.notEqual(FF.property("att"), FF.literal("15")), filter);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThan() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att > 15");
-        assertEquals(FF.greater(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThanOrEqualTo() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att >= 15");
-        assertEquals(FF.greaterOrEqual(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsLessThan() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att < 15");
-        assertEquals(FF.less(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsLessThanOrEqualTo() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att <= 15");
-        assertEquals(FF.lessOrEqual(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsLike() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att LIKE '%hello_'");
-        assertEquals(FF.like(FF.property("att"), "%hello_", '%', '_', '\\', true), filter);
-    }
-
-    @Test
-    public void testPropertyIsNotLike() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att NOT LIKE '%hello_'");
-        assertEquals(FF.not(FF.like(FF.property("att"), "%hello_", '%', '_', '\\', true)), filter);
-    }
-
-    @Test
-    public void testPropertyIsLikeInsensitive() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att ILIKE '%hello_'");
-        assertEquals(FF.like(FF.property("att"),"%hello_", '%', '_', '\\', false), filter);
-    }
-
-    @Test
-    public void testPropertyIsNull() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att IS NULL");
-        assertEquals(FF.isNull(FF.property("att")), filter);
-    }
-
-    @Test
-    public void testPropertyIsNotNull() throws CQLException {
-        final Filter<?> filter = CQL.parseFilter("att IS NOT NULL");
-        assertEquals(FF.not(FF.isNull(FF.property("att"))), filter);
-    }
-
-    @Test
-    public void testBBOX1() throws CQLException {
-        final Envelope2D env = new Envelope2D(null, 10, 20, 30-10, 40-20);
-        testBBOX("BBOX(\"att\", 10, 20, 30, 40)", "att", env);
-    }
-
-    @Test
-    public void testBBOX2() throws CQLException {
-        final Envelope2D env = new Envelope2D(CommonCRS.WGS84.normalizedGeographic(), 10, 20, 30-10, 40-20);
-        testBBOX("BBOX(\"att\", 10, 20, 30, 40, 'CRS:84')", "att", env);
-    }
-
-    @Test
-    public void testBBOX3() throws CQLException {
-        final Envelope2D env = new Envelope2D(CommonCRS.WGS84.normalizedGeographic(), 10, 20, 30-10, 40-20);
-        testBBOX("BBOX(att, 10, 20, 30, 40, 'CRS:84')", "att", env);
-    }
-
-    @Test
-    public void testBBOX4() throws CQLException {
-        final Envelope2D env = new Envelope2D(null, -10, -20, 10 - -10, 20 - -20);
-        testBBOX("BBOX(geometry,-10,-20,10,20)", "geometry", env);
-    }
-
-    private void testBBOX(final String cql, final String att, final Envelope env) throws CQLException {
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertInstanceOf(null, BinarySpatialOperator.class, filter);
-        assertEquals(SpatialOperatorName.BBOX, filter.getOperatorType());
-        assertEquals(FF.property(att), ((BinarySpatialOperator)filter).getOperand1());
-        assertEquals(FF.property(att), ((BinarySpatialOperator)filter).getOperand1());
-        final Expression<?, ?> arg2 = ((BinarySpatialOperator) filter).getOperand2();
-        assertTrue("Second argument should be a literal expression", arg2 instanceof Literal);
-        final AbstractEnvelope argEnv = AbstractEnvelope.castOrCopy((Envelope) ((Literal<?, ?>) arg2).getValue());
-        assertTrue(argEnv.equals(env, 1e-2, false));
-    }
-
-    /**
-     * Tests {@code DWITHIN}.
-     */
-    @Test
-    public void testDWithin() throws CQLException {
-        final String cql = "DWITHIN(BoundingBox, POINT(12.1 28.9), 10, 'meters')";
-        final DistanceOperator<?> filter = (DistanceOperator<?>) CQL.parseFilter(cql);
-        assertEquals(DistanceOperatorName.WITHIN, filter.getOperatorType());
-
-        final List expressions = filter.getExpressions();
-        assertEquals(FF.property("BoundingBox"), expressions.get(0));
-        final Quantity<Length> distance = filter.getDistance();
-        assertEquals(10.0, distance.getValue().doubleValue(), DELTA);
-        assertEquals(Units.METRE, distance.getUnit());
-
-        final Literal<?,?> literal = (Literal<?,?>) expressions.get(1);
-        final Geometry value = (Geometry) literal.getValue();
-        assertTrue(baseGeometryPoint.equalsExact(value));
-    }
-
-    @Test
-    public void testBinarySpatialOperators() throws CQLException {
-        for (final SpatialOperatorName operator : SpatialOperatorName.values()) {
-            if (operator == SpatialOperatorName.BBOX) continue;
-            testSpatialOperators(operator, "");
-            if (operator == SpatialOperatorName.OVERLAPS) break;
-        }
-    }
-
-    @Test
-    public void testDistanceOperators() throws CQLException {
-        for (final DistanceOperatorName operator : DistanceOperatorName.values()) {
-            final DistanceOperator<?> filter = (DistanceOperator<?>) testSpatialOperators(operator, ", 10, 'meters'");
-            final Quantity<Length> distance = filter.getDistance();
-            assertEquals(10.0, distance.getValue().doubleValue(), DELTA);
-            assertEquals(Units.METRE, distance.getUnit());
-            if (operator == DistanceOperatorName.WITHIN) break;
-        }
-    }
-
-    private Filter<?> testSpatialOperators(final CodeList<?> operator, final String suffix) throws CQLException {
-        final String name = operator.identifier().toUpperCase(Locale.US);
-        final String cql = name + "(\"att\", POLYGON((10 20, 30 40, 50 60, 10 20))" + suffix + ')';
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertInstanceOf(name, SpatialOperator.class, filter);
-        assertEquals(name, operator, filter.getOperatorType());
-
-        final List expressions = filter.getExpressions();
-        assertEquals(FF.property("att"), expressions.get(0));
-        final Literal<?,?> literal = (Literal<?,?>) expressions.get(1);
-        final Geometry value = (Geometry) literal.getValue();
-        assertTrue(baseGeometry.equalsExact(value));
-        return filter;
-    }
-
-    @Test
-    public void testCombine1() throws CQLException {
-        testCombine("NOT att = 15 OR att BETWEEN 15 AND 30");
-    }
-
-    @Test
-    public void testCombine2() throws CQLException {
-        testCombine("(NOT att = 15) OR (att BETWEEN 15 AND 30)");
-    }
-
-    private void testCombine(final String cql) throws CQLException {
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertInstanceOf("OR", LogicalOperator.class, filter);
-        assertEquals(LogicalOperatorName.OR, filter.getOperatorType());
-        assertEquals(
-                FF.or(
-                    FF.not(FF.equal(FF.property("att"), FF.literal(15))),
-                    FF.between(FF.property("att"), FF.literal(15), FF.literal(30))
-                ),
-                filter
-                );
-    }
-
-    @Test
-    public void testCombine3() throws CQLException {
-        final String cql = "(NOT att1 = 15) AND (att2 = 15 OR att3 BETWEEN 15 AND 30) AND (att4 BETWEEN 1 AND 2)";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertInstanceOf("AND", LogicalOperator.class, filter);
-        assertEquals(LogicalOperatorName.AND, filter.getOperatorType());
-        assertEquals(
-                FF.and(
-                    UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
-                        FF.not(FF.equal(FF.property("att1"), FF.literal(15))),
-                        FF.or(
-                            FF.equal(FF.property("att2"), FF.literal(15)),
-                            FF.between(FF.property("att3"), FF.literal(15), FF.literal(30))
-                        ),
-                        FF.between(FF.property("att4"), FF.literal(1), FF.literal(2))
-                    })
-                ),
-                filter
-                );
-    }
-
-    @Test
-    @Ignore("Mismatched type in `property(…, type)`.")
-    public void testCombine4() throws CQLException {
-        final String cql = "(x+7) <= (y-9)";
-        final Filter<?> filter = CQL.parseFilter(cql);
-        assertInstanceOf("<=", BinaryComparisonOperator.class, filter);
-        assertEquals(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO, filter.getOperatorType());
-        assertEquals(
-                FF.lessOrEqual(
-                    FF.add(FF.property("x", Number.class), FF.literal(7)),
-                    FF.subtract(FF.property("y", Number.class), FF.literal(9))
-                ),
-                filter
-                );
-    }
-
-    @Test
-    public void testTemporalOperators() throws CQLException, ParseException {
-        for (final TemporalOperatorName operator : TemporalOperatorName.values()) {
-            final String name = operator.identifier().toUpperCase(Locale.US);
-            final String cql  = "att " + name + " 2012-03-21T05:42:36Z";
-            final Filter<?> filter = CQL.parseFilter(cql);
-            assertInstanceOf(name, TemporalOperator.class, filter);
-            assertEquals(name, operator, filter.getOperatorType());
-            final List expressions = filter.getExpressions();
-
-            assertEquals(name, FF.property("att"), expressions.get(0));
-            final Literal<?,?> literal = (Literal<?,?>) expressions.get(1);
-            final TemporalAccessor time = (TemporalAccessor) literal.getValue();
-            assertEquals(name, Instant.parse("2012-03-21T05:42:36Z"), Instant.from(time));
-
-            // Skip any non-standard operators added by user.
-            if (operator == TemporalOperatorName.ANY_INTERACTS) break;
-        }
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterWritingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/FilterWritingTest.java
deleted file mode 100644
index 3731dc3..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterWritingTest.java
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * 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.cql;
-
-import java.time.Instant;
-import java.text.ParseException;
-import org.opengis.filter.Filter;
-import org.opengis.feature.Feature;
-import org.apache.sis.geometry.Envelope2D;
-import org.apache.sis.internal.util.UnmodifiableArrayList;
-import org.apache.sis.measure.Quantities;
-import org.apache.sis.measure.Units;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-import org.junit.Ignore;
-
-// Optional dependencies
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LinearRing;
-
-
-/**
- * Test writing in CQL filters.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class FilterWritingTest extends CQLTestCase {
-
-    private final Geometry baseGeometry = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[0]
-                );
-
-    @Test
-    public void testExcludeFilter() throws CQLException {
-        final Filter filter = Filter.exclude();
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("1=0", cql);
-    }
-
-    @Test
-    public void testIncludeFilter() throws CQLException {
-        final Filter filter = Filter.include();
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("1=1", cql);
-    }
-
-    @Test
-    public void testAnd() throws CQLException {
-        final Filter filter = FF.and(
-                UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
-                    FF.equal(FF.property("att1"), FF.literal(15)),
-                    FF.equal(FF.property("att2"), FF.literal(30)),
-                    FF.equal(FF.property("att3"), FF.literal(50))
-                }));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("(\"att1\" = 15 AND \"att2\" = 30 AND \"att3\" = 50)", cql);
-    }
-
-    @Test
-    public void testOr() throws CQLException {
-        final Filter filter = FF.or(
-                UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
-                    FF.equal(FF.property("att1"), FF.literal(15)),
-                    FF.equal(FF.property("att2"), FF.literal(30)),
-                    FF.equal(FF.property("att3"), FF.literal(50))
-                }));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("(\"att1\" = 15 OR \"att2\" = 30 OR \"att3\" = 50)", cql);
-    }
-
-    @Test
-    public void testId() throws CQLException {
-        final Filter filter = FF.resourceId("test-1");
-        try {
-            CQL.write(filter);
-            fail("ID filter does not exist in CQL");
-        } catch (UnsupportedOperationException ex) {
-            assertTrue(ex.getMessage().contains("ID"));
-        }
-    }
-
-    @Test
-    public void testNot() throws CQLException {
-        final Filter filter = FF.not(FF.equal(FF.property("att"), FF.literal(15)));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("NOT att = 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsBetween() throws CQLException {
-        final Filter filter = FF.between(FF.property("att"), FF.literal(15), FF.literal(30));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BETWEEN 15 AND 30", cql);
-    }
-
-    @Test
-    public void testPropertyIsEqualTo() throws CQLException {
-        final Filter filter = FF.equal(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att = 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsNotEqualTo() throws CQLException {
-        final Filter filter = FF.notEqual(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att <> 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThan() throws CQLException {
-        final Filter filter = FF.greater(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att > 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThanOrEqualTo() throws CQLException {
-        final Filter filter = FF.greaterOrEqual(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att >= 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsLessThan() throws CQLException {
-        final Filter filter = FF.less(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att < 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsLessThanOrEqualTo() throws CQLException {
-        final Filter filter = FF.lessOrEqual(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att <= 15", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testPropertyIsLike() throws CQLException {
-        final Filter filter = FF.like(FF.property("att"),"%hello");
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ILIKE '%hello'", cql);
-    }
-
-    @Test
-    public void testPropertyIsNull() throws CQLException {
-        final Filter filter = FF.isNull(FF.property("att"));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att IS NULL", cql);
-    }
-
-    @Test
-    public void testBBOX() throws CQLException {
-        final Filter filter = FF.bbox(FF.property("att"), new Envelope2D(null, 10, 20, 30-10, 40-20));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("BBOX(att, 10.0, 30.0, 20.0, 40.0)", cql);
-    }
-
-    @Test
-    public void testBeyond() throws CQLException {
-        final Filter filter = FF.beyond(FF.property("att"), FF.literal(baseGeometry), Quantities.create(3, Units.METRE));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("BEYOND(att, POLYGON ((10 20, 30 40, 50 60, 10 20)), 3.0, 'meter')", cql);
-    }
-
-    @Test
-    public void testContains() throws CQLException {
-        final Filter filter = FF.contains(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("CONTAINS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testCrosses() throws CQLException {
-        final Filter filter = FF.crosses(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("CROSSES(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testDisjoint() throws CQLException {
-        final Filter filter = FF.disjoint(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("DISJOINT(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testDWithin() throws CQLException {
-        final Filter filter = FF.within(FF.property("att"), FF.literal(baseGeometry), Quantities.create(2, Units.METRE));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("DWITHIN(att, POLYGON ((10 20, 30 40, 50 60, 10 20)), 2.0, 'meter')", cql);
-    }
-
-    @Test
-    public void testEquals() throws CQLException {
-        final Filter filter = FF.equals(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("EQUALS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testIntersects() throws CQLException {
-        final Filter filter = FF.intersects(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("INTERSECTS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testOverlaps() throws CQLException {
-        final Filter filter = FF.overlaps(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("OVERLAPS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testTouches() throws CQLException {
-        final Filter filter = FF.touches(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("TOUCHES(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Test
-    public void testWithin() throws CQLException {
-        final Filter filter = FF.within(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("WITHIN(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testAfter() throws CQLException, ParseException {
-        final Filter filter = FF.after(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att AFTER 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testAnyInteracts() throws CQLException, ParseException {
-        final Filter filter = FF.anyInteracts(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ANYINTERACTS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testBefore() throws CQLException, ParseException {
-        final Filter filter = FF.before(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BEFORE 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testBegins() throws CQLException, ParseException {
-        final Filter filter = FF.begins(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BEGINS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testBegunBy() throws CQLException, ParseException {
-        final Filter filter = FF.begunBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BEGUNBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testDuring() throws CQLException, ParseException {
-        final Filter filter = FF.during(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att DURING 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testEndedBy() throws CQLException, ParseException {
-        final Filter filter = FF.endedBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ENDEDBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testEnds() throws CQLException, ParseException {
-        final Filter filter = FF.ends(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ENDS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testMeets() throws CQLException, ParseException {
-        final Filter filter = FF.meets(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att MEETS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testMetBy() throws CQLException, ParseException {
-        final Filter filter = FF.metBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att METBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testOverlappedBy() throws CQLException, ParseException {
-        final Filter filter = FF.overlappedBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att OVERLAPPEDBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testTcontains() throws CQLException, ParseException {
-        final Filter filter = FF.tcontains(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att TCONTAINS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testTequals() throws CQLException, ParseException {
-        final Filter filter = FF.tequals(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att TEQUALS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testToverlaps() throws CQLException, ParseException {
-        final Filter filter = FF.toverlaps(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att TOVERLAPS 2012-03-21T05:42:36Z", cql);
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/QueryReadingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/QueryReadingTest.java
deleted file mode 100644
index 0089fed..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/QueryReadingTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.cql;
-
-import java.util.Arrays;
-import static org.junit.Assert.assertEquals;
-import org.junit.Test;
-import org.opengis.filter.SortOrder;
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final strictfp class QueryReadingTest extends CQLTestCase {
-
-    @Test
-    public void testEmpty() throws CQLException {
-        String cql = "SELECT *";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(), query);
-    }
-
-    @Test
-    public void testProjections() throws CQLException {
-        String cql = "SELECT \"name\", 4 as 'col1'";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(Arrays.asList(new Query.Projection(FF.property("name"), null), new Query.Projection(FF.literal(4),"col1")), null, null, null, null), query);
-    }
-
-    @Test
-    public void testWhere() throws CQLException {
-        String cql = "SELECT * WHERE \"id\" = 'a'";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(null, FF.equal(FF.property("id"), FF.literal("a")), null, null, null), query);
-    }
-
-    @Test
-    public void testOffset() throws CQLException {
-        String cql = "SELECT * OFFSET 5";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(null, null, null, 5, null), query);
-    }
-
-    @Test
-    public void testLimit() throws CQLException {
-        String cql = "SELECT * LIMIT 10";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(null, null, null, null, 10), query);
-    }
-
-    @Test
-    public void testOrderBy() throws CQLException {
-        String cql = "SELECT * ORDER BY \"name\" ASC, \"age\" DESC";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(null, null, Arrays.asList(
-                FF.sort(FF.property("name"), SortOrder.ASCENDING),
-                FF.sort(FF.property("age"), SortOrder.DESCENDING)),
-                null, null), query);
-    }
-
-    @Test
-    public void testOrderByDefault() throws CQLException {
-        String cql = "SELECT * ORDER BY \"name\", \"age\"";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(new Query(null, null, Arrays.asList(
-                FF.sort(FF.property("name"), SortOrder.ASCENDING),
-                FF.sort(FF.property("age"), SortOrder.ASCENDING)),
-                null, null), query);
-    }
-
-    @Test
-    public void testComplete() throws CQLException {
-        String cql = "SELECT \"name\", 4 as 'col1' WHERE \"id\" = 'a' ORDER BY \"name\" ASC, \"age\" DESC OFFSET 5 LIMIT 10";
-        Query query = CQL.parseQuery(cql);
-        assertEquals(
-                new Query(
-                        Arrays.asList(new Query.Projection(FF.property("name"), null), new Query.Projection(FF.literal(4),"col1")),
-                        FF.equal(FF.property("id"), FF.literal("a")),
-                        Arrays.asList(
-                            FF.sort(FF.property("name"), SortOrder.ASCENDING),
-                            FF.sort(FF.property("age"), SortOrder.DESCENDING)),
-                        5,
-                        10),
-                query);
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/QueryWritingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/QueryWritingTest.java
deleted file mode 100644
index 75b0d97..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/QueryWritingTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.cql;
-
-import java.util.Arrays;
-import static org.junit.Assert.assertEquals;
-import org.junit.Test;
-import org.opengis.filter.SortOrder;
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final strictfp class QueryWritingTest extends CQLTestCase {
-
-    @Test
-    public void testWrite() throws CQLException {
-        final Query query = new Query(
-                Arrays.asList(new Query.Projection(FF.property("name"), null), new Query.Projection(FF.literal(4),"col1")),
-                FF.equal(FF.property("id"), FF.literal("a")),
-                Arrays.asList(
-                    FF.sort(FF.property("name"), SortOrder.ASCENDING),
-                    FF.sort(FF.property("age"), SortOrder.DESCENDING)),
-                5,
-                10);
-
-        String cql = CQL.write(query);
-        assertEquals("SELECT name, 4 AS 'col1' WHERE id = 'a' ORDER BY name ASC, age DESC OFFSET 5 LIMIT 10", cql);
-    }
-}
diff --git a/core/sis-feature/pom.xml b/core/sis-feature/pom.xml
index 05f39c7..0f4a24e 100644
--- a/core/sis-feature/pom.xml
+++ b/core/sis-feature/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/BandedCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/BandedCoverage.java
index 11d4140..db74ab3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/BandedCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/BandedCoverage.java
@@ -21,8 +21,6 @@
 import java.util.function.Function;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/CannotEvaluateException.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/CannotEvaluateException.java
new file mode 100644
index 0000000..9dbc699
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/CannotEvaluateException.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.coverage;
+
+
+/**
+ * Thrown when a quantity can not be evaluated.
+ *
+ * <div class="note"><b>Upcoming API change:</b>
+ * this class may move to GeoAPI in a future version. If that move happens,
+ * the {@code org.apache.sis.coverage} package name would become {@code org.opengis.coverage}.</div>
+ */
+public class CannotEvaluateException extends RuntimeException {
+    /**
+     * Creates an exception with no message.
+     */
+    public CannotEvaluateException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message  the detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     */
+    public CannotEvaluateException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message  the detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     * @param  cause  the cause for this exception. The cause is saved
+     *         for later retrieval by the {@link #getCause()} method.
+     */
+    public CannotEvaluateException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/PointOutsideCoverageException.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/PointOutsideCoverageException.java
new file mode 100644
index 0000000..9f73c0d
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/PointOutsideCoverageException.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.coverage;
+
+
+/**
+ * Thrown when an evaluate method is invoked for a location outside the domain of the coverage.
+ *
+ * <div class="note"><b>Upcoming API change:</b>
+ * this class may move to GeoAPI in a future version. If that move happens,
+ * the {@code org.apache.sis.coverage} package name would become {@code org.opengis.coverage}.</div>
+ */
+public class PointOutsideCoverageException extends CannotEvaluateException {
+    /**
+     * Creates an exception with no message.
+     */
+    public PointOutsideCoverageException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message  the detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     */
+    public PointOutsideCoverageException(final String message) {
+        super(message);
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/RegionOfInterest.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/RegionOfInterest.java
index fb82f68..45b0cdc 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/RegionOfInterest.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/RegionOfInterest.java
@@ -110,7 +110,7 @@
             }
             crsToGrid = MathTransforms.bidimensional(tr);
         } catch (IllegalArgumentException | FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(null, e);
         }
         return crsToGrid.createTransformedShape(geometry);
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
index 3341b59..56cf885 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@ -65,7 +65,7 @@
  * </div>
  *
  * <h2>Relationship with metadata</h2>
- * This class provides the same information than ISO 19115 {@link org.opengis.metadata.content.SampleDimension},
+ * This class provides the same information than ISO 19115 {@code org.opengis.metadata.content.SampleDimension},
  * but organized in a different way. The use of the same name may seem a risk, but those two types are typically
  * not used at the same time.
  *
@@ -78,9 +78,6 @@
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Alexis Manin (Geomatys)
  * @version 1.2
- *
- * @see org.opengis.metadata.content.SampleDimension
- *
  * @since 1.0
  * @module
  */
@@ -520,7 +517,7 @@
      *
      * <p>A <cite>quantitative category</cite> is a range of sample values associated to numbers with units of measurement.
      * For example 10 = 1.0°C, 11 = 1.1°C, 12 = 1.2°C, <i>etc</i>. A quantitative category has a
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getTransferFunctionType() transfer function}
+     * transfer function
      * (typically a scale factor and an offset) for converting sample values to values expressed
      * in the unit of measurement.</p>
      *
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
index df8037e..a13e42e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.coverage;
 
-import org.opengis.coverage.CannotEvaluateException;
-
 
 /**
  * Thrown when an operation can only be applied on a subspace of a multi-dimensional coverage,
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
index 1be0b6f..646eb2d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
@@ -40,8 +40,8 @@
 
 // Branch-specific imports
 import org.apache.sis.internal.jdk9.JDK9;
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
+import org.apache.sis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -353,7 +353,7 @@
                             return null;
                         }
                         throw new PointOutsideCoverageException(
-                                gc.pointOutsideCoverage(getCoverage().gridGeometry.extent), point);
+                                gc.pointOutsideCoverage(getCoverage().gridGeometry.extent));
                     }
                     /*
                      * Following should never overflow, otherwise BufferedGridCoverage
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
index b3f2b6d..3644a3b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
@@ -33,7 +33,7 @@
 import org.apache.sis.image.ImageProcessor;
 
 // Branch-dependent imports
-import org.opengis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DefaultEvaluator.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DefaultEvaluator.java
index aede5d3..ac85973 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DefaultEvaluator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DefaultEvaluator.java
@@ -50,8 +50,8 @@
 import static java.util.logging.Logger.getLogger;
 
 // Branch-dependent imports
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
+import org.apache.sis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -384,11 +384,10 @@
             } catch (ArithmeticException | IndexOutOfBoundsException | DisjointExtentException ex) {
                 if (!nullIfOutside) {
                     throw (PointOutsideCoverageException) new PointOutsideCoverageException(
-                            gc.pointOutsideCoverage(gridGeometry.extent), point).initCause(ex);
+                            gc.pointOutsideCoverage(gridGeometry.extent)).initCause(ex);
                 }
             }
         } catch (PointOutsideCoverageException ex) {
-            ex.setOffendingLocation(point);
             throw ex;
         } catch (RuntimeException | FactoryException | TransformException ex) {
             throw new CannotEvaluateException(ex.getMessage(), ex);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java
index e9f74b6..c55f07d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java
@@ -197,7 +197,7 @@
             if (cause instanceof TransformException) {
                 throw (TransformException) cause;
             }
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
         return gg;
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/EvaluatorWrapper.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/EvaluatorWrapper.java
index 014335b..1d685fa 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/EvaluatorWrapper.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/EvaluatorWrapper.java
@@ -21,7 +21,7 @@
 import org.opengis.referencing.operation.TransformException;
 
 // Branch-dependent imports
-import org.opengis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/FractionalGridCoordinates.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/FractionalGridCoordinates.java
index 2995c44..1001b77 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/FractionalGridCoordinates.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/FractionalGridCoordinates.java
@@ -20,8 +20,6 @@
 import java.io.Serializable;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.coverage.grid.GridCoordinates;
-import org.opengis.coverage.PointOutsideCoverageException;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -30,6 +28,7 @@
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.util.StringBuilders;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -55,7 +54,7 @@
  * @since 1.1
  * @module
  */
-public class FractionalGridCoordinates implements GridCoordinates, Serializable {
+public class FractionalGridCoordinates implements Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -94,7 +93,6 @@
      *
      * @return  the number of dimensions.
      */
-    @Override
     public int getDimension() {
         return coordinates.length;
     }
@@ -109,7 +107,6 @@
      * @throws ArithmeticException if a coordinate value is outside the range
      *         of values representable as a 64-bits integer value.
      */
-    @Override
     public long[] getCoordinateValues() {
         final long[] indices = new long[coordinates.length];
         for (int i=0; i<indices.length; i++) {
@@ -132,7 +129,6 @@
      * @throws ArithmeticException if the coordinate value is outside the range
      *         of values representable as a 64-bits integer value.
      */
-    @Override
     public long getCoordinateValue(final int dimension) {
         final double value = coordinates[dimension];
         /*
@@ -170,7 +166,6 @@
      * @throws ArithmeticException if this method can not store the given grid coordinate
      *         without precision lost.
      */
-    @Override
     public void setCoordinateValue(final int dimension, final long value) {
         if ((coordinates[dimension] = value) != value) {
             throw new ArithmeticException(Resources.format(Resources.Keys.UnconvertibleGridCoordinate_2, "double", value));
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java
index 77afa56..09e1193 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java
@@ -17,8 +17,6 @@
 package org.apache.sis.coverage.grid;
 
 import java.util.Arrays;
-import org.opengis.coverage.grid.GridCoordinates;
-import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.jdk9.JDK9;
 
@@ -27,12 +25,16 @@
  * A view over the low or high grid envelope coordinates.
  * This is not a general-purpose grid coordinates since it assumes a {@link GridExtent} coordinates layout.
  *
+ * <div class="note"><b>Upcoming API generalization:</b>
+ * this class may implement the {@code GridCoordinates} interface in a future Apache SIS version.
+ * This is pending GeoAPI update.</div>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  * @since   1.0
  * @module
  */
-final class GridCoordinatesView implements GridCoordinates {
+final class GridCoordinatesView {
     /**
      * A reference to the coordinate array of the enclosing grid envelope.
      */
@@ -55,7 +57,6 @@
     /**
      * Returns the number of dimension.
      */
-    @Override
     public final int getDimension() {
         return coordinates.length >>> 1;
     }
@@ -63,7 +64,6 @@
     /**
      * Returns all coordinate values.
      */
-    @Override
     public final long[] getCoordinateValues() {
         return Arrays.copyOfRange(coordinates, offset, offset + getDimension());
     }
@@ -71,7 +71,6 @@
     /**
      * Returns the coordinate value for the specified dimension.
      */
-    @Override
     public final long getCoordinateValue(final int index) {
         ArgumentChecks.ensureValidIndex(getDimension(), index);
         return coordinates[offset + index];
@@ -80,10 +79,9 @@
     /**
      * Do not allow modification of grid coordinates since they are backed by {@link GridExtent}.
      */
-    @Override
-    public void setCoordinateValue(final int index, long value) {
-        throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, "GridCoordinates"));
-    }
+//  public void setCoordinateValue(final int index, long value) {
+//      throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, "GridCoordinates"));
+//  }
 
     /**
      * Returns a string representation of this grid coordinates for debugging purpose.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index 1feabdb..2e51512 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -33,6 +33,7 @@
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.coverage.BandedCoverage;
 import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.CannotEvaluateException;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
 import org.apache.sis.image.DataType;
 import org.apache.sis.image.ImageProcessor;
@@ -46,9 +47,6 @@
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Debug;
 
-// Branch-specific imports
-import org.opengis.coverage.CannotEvaluateException;
-
 
 /**
  * Base class of coverages with domains defined as a set of grid points.
@@ -466,7 +464,7 @@
      * @throws DisjointExtentException if the given extent does not intersect this grid coverage.
      * @throws CannotEvaluateException if this method can not produce the rendered image for another reason.
      */
-    public abstract RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException;
+    public abstract RenderedImage render(GridExtent sliceExtent);
 
     /**
      * Returns a string representation of this grid coverage for debugging purpose.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index c5d9337..e5b5d26 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -59,8 +59,8 @@
 import static java.lang.Math.toIntExact;
 
 // Branch-specific imports
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
+import org.apache.sis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -541,10 +541,9 @@
                         return null;
                     }
                     throw (PointOutsideCoverageException) new PointOutsideCoverageException(
-                            gc.pointOutsideCoverage(gridGeometry.extent), point).initCause(ex);
+                            gc.pointOutsideCoverage(gridGeometry.extent)).initCause(ex);
                 }
             } catch (PointOutsideCoverageException ex) {
-                ex.setOffendingLocation(point);
                 throw ex;
             } catch (RuntimeException | FactoryException | TransformException ex) {
                 throw new CannotEvaluateException(ex.getMessage(), ex);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
index 031942f..a1df342 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -473,7 +473,7 @@
                 throw e;
             }
         } catch (FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
         return resampled.forConvertedValues(isConverted);
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
index d1eb24b..4e145d5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
@@ -52,9 +52,7 @@
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.StringBuilders;
 import org.apache.sis.math.MathFunctions;
-
-// Branch-dependent imports
-import org.opengis.coverage.PointOutsideCoverageException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index e71d284..727897c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -65,15 +65,11 @@
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.internal.system.Modules;
+import org.apache.sis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 
 import static java.util.logging.Logger.getLogger;
 
-// Branch-dependent imports
-import org.opengis.coverage.grid.GridEnvelope;
-import org.opengis.coverage.grid.GridCoordinates;
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
-
 
 /**
  * A range of grid coverage coordinates, also known as "grid envelope".
@@ -89,13 +85,17 @@
  * <p>{@code GridExtent} instances are immutable and thread-safe.
  * The same instance can be shared by different {@link GridGeometry} instances.</p>
  *
+ * <div class="note"><b>Upcoming API generalization:</b>
+ * this class may implement the {@code GridEnvelope} interface in a future Apache SIS version.
+ * This is pending GeoAPI update.</div>
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Alexis Manin (Geomatys)
  * @version 1.3
  * @since   1.0
  * @module
  */
-public class GridExtent implements GridEnvelope, LenientComparable, Serializable {
+public class GridExtent implements Serializable, LenientComparable {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -591,50 +591,12 @@
     }
 
     /**
-     * Creates a new grid extent as a copy of the given one.
-     *
-     * @param  extent  the grid extent to copy.
-     * @throws IllegalArgumentException if a coordinate value in the low part is
-     *         greater than the corresponding coordinate value in the high part.
-     *
-     * @see #castOrCopy(GridEnvelope)
-     */
-    protected GridExtent(final GridEnvelope extent) {
-        ArgumentChecks.ensureNonNull("extent", extent);
-        final int dimension = extent.getDimension();
-        coordinates = allocate(dimension);
-        for (int i=0; i<dimension; i++) {
-            coordinates[i] = extent.getLow(i);
-            coordinates[i + dimension] = extent.getHigh(i);
-        }
-        types = (extent instanceof GridExtent) ? ((GridExtent) extent).types : null;
-        validateCoordinates();
-    }
-
-    /**
-     * Returns the given grid extent as a {@code GridExtent} implementation.
-     * If the given extent is already a {@code GridExtent} instance or is null, then it is returned as-is.
-     * Otherwise a new extent is created using the {@linkplain #GridExtent(GridEnvelope) copy constructor}.
-     *
-     * @param  extent  the grid extent to cast or copy, or {@code null}.
-     * @return the grid extent as a {@code GridExtent}, or {@code null} if the given extent was null.
-     */
-    public static GridExtent castOrCopy(final GridEnvelope extent) {
-        if (extent == null || extent instanceof GridExtent) {
-            return (GridExtent) extent;
-        } else {
-            return new GridExtent(extent);
-        }
-    }
-
-    /**
      * Returns the number of dimensions.
      *
      * @return the number of dimensions.
      *
      * @see #reduceDimension(int[])
      */
-    @Override
     public final int getDimension() {
         return coordinates.length >>> 1;
     }
@@ -692,8 +654,7 @@
      *
      * @see #getLow(int)
      */
-    @Override
-    public GridCoordinates getLow() {
+    GridCoordinatesView getLow() {
         return new GridCoordinatesView(coordinates, 0);
     }
 
@@ -705,8 +666,7 @@
      *
      * @see #getHigh(int)
      */
-    @Override
-    public GridCoordinates getHigh() {
+    GridCoordinatesView getHigh() {
         return new GridCoordinatesView(coordinates, getDimension());
     }
 
@@ -718,11 +678,9 @@
      * @throws IndexOutOfBoundsException if the given index is negative or is equal or greater
      *         than the {@linkplain #getDimension() grid dimension}.
      *
-     * @see #getLow()
      * @see #getHigh(int)
      * @see #withRange(int, long, long)
      */
-    @Override
     public long getLow(final int index) {
         ArgumentChecks.ensureValidIndex(getDimension(), index);
         return coordinates[index];
@@ -736,11 +694,9 @@
      * @throws IndexOutOfBoundsException if the given index is negative or is equal or greater
      *         than the {@linkplain #getDimension() grid dimension}.
      *
-     * @see #getHigh()
      * @see #getLow(int)
      * @see #withRange(int, long, long)
      */
-    @Override
     public long getHigh(final int index) {
         final int dimension = getDimension();
         ArgumentChecks.ensureValidIndex(dimension, index);
@@ -761,7 +717,6 @@
      * @see #getHigh(int)
      * @see #resize(long...)
      */
-    @Override
     public long getSize(final int index) {
         final int dimension = getDimension();
         ArgumentChecks.ensureValidIndex(dimension, index);
@@ -892,7 +847,7 @@
      * If there is less than <var>s</var> dimensions having a size greater than 1, then the returned list of
      * dimensions is completed with some dimensions of size 1, starting with the first dimensions in this grid
      * extent, until there is exactly <var>s</var> dimensions. If this grid extent does not have at least
-     * <var>s</var> dimensions, then a {@link CannotEvaluateException} is thrown.
+     * <var>s</var> dimensions, then a {@code CannotEvaluateException} is thrown.
      *
      * @param  s  number of dimensions of the sub-space.
      * @return indices of sub-space dimensions, in increasing order in an array of length <var>s</var>.
@@ -1038,7 +993,7 @@
         if (gridToCRS != null && Matrices.isAffine(gridToCRS)) try {
             envelope.setCoordinateReferenceSystem(GridExtentCRS.forExtentAlone(gridToCRS, getAxisTypes()));
         } catch (FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
         return envelope;
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index 6456f23..2e6bf42 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -970,7 +970,7 @@
             }
             return env;
         } catch (FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(null, e);
         }
         throw incomplete(bitmask, errorKey);
     }
@@ -1536,7 +1536,7 @@
         try {
             tr = finder.inverse();
         } catch (FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
         return MathTransforms.concatenate(getGridToCRS(anchor), tr);
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
index 92ffd45..73c7757 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -21,7 +21,6 @@
 import java.awt.Rectangle;
 import java.awt.image.RenderedImage;
 import org.opengis.util.FactoryException;
-import org.opengis.coverage.CannotEvaluateException;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
@@ -42,6 +41,7 @@
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Utilities;
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/TranslatedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/TranslatedGridCoverage.java
index 81bb354..bc4ece6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/TranslatedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/TranslatedGridCoverage.java
@@ -19,7 +19,7 @@
 import java.awt.image.RenderedImage;
 
 // Branch-dependent imports
-import org.opengis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
index 3b1680f..480544c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
@@ -24,14 +24,6 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.feature.Resources;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.MultiValuedPropertyException;
-
 
 /**
  * An instance of an {@linkplain DefaultAssociationRole feature association role} containing the associated feature.
@@ -55,7 +47,7 @@
  * @since 0.5
  * @module
  */
-public abstract class AbstractAssociation extends Field<Feature> implements FeatureAssociation, Cloneable, Serializable {
+public abstract class AbstractAssociation extends Field<AbstractFeature> implements Cloneable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -64,16 +56,16 @@
     /**
      * Information about the association.
      */
-    final FeatureAssociationRole role;
+    final DefaultAssociationRole role;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role  information about the association.
      *
-     * @see #create(FeatureAssociationRole)
+     * @see #create(DefaultAssociationRole)
      */
-    protected AbstractAssociation(final FeatureAssociationRole role) {
+    protected AbstractAssociation(final DefaultAssociationRole role) {
         this.role = role;
     }
 
@@ -85,7 +77,7 @@
      *
      * @see DefaultAssociationRole#newInstance()
      */
-    public static AbstractAssociation create(final FeatureAssociationRole role) {
+    public static AbstractAssociation create(final DefaultAssociationRole role) {
         ArgumentChecks.ensureNonNull("role", role);
         return isSingleton(role.getMaximumOccurs())
                ? new SingletonAssociation(role)
@@ -99,16 +91,16 @@
      * @param  value  the initial value (may be {@code null}).
      * @return the new association.
      */
-    static AbstractAssociation create(final FeatureAssociationRole role, final Object value) {
+    static AbstractAssociation create(final DefaultAssociationRole role, final Object value) {
         ArgumentChecks.ensureNonNull("role", role);
         return isSingleton(role.getMaximumOccurs())
-               ? new SingletonAssociation(role, (Feature) value)
+               ? new SingletonAssociation(role, (AbstractFeature) value)
                : new MultiValuedAssociation(role, value);
     }
 
     /**
      * Returns the name of this association as defined by its {@linkplain #getRole() role}.
-     * This convenience method delegates to {@link FeatureAssociationRole#getName()}.
+     * This convenience method delegates to {@link DefaultAssociationRole#getName()}.
      *
      * @return the association name specified by its role.
      */
@@ -120,10 +112,12 @@
     /**
      * Returns information about the association.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.AssociationRole}. This change is pending GeoAPI revision.</div>
+     *
      * @return information about the association.
      */
-    @Override
-    public FeatureAssociationRole getRole() {
+    public DefaultAssociationRole getRole() {
         return role;
     }
 
@@ -132,13 +126,16 @@
      * the common case where the {@linkplain DefaultAssociationRole#getMaximumOccurs() maximum number} of
      * features is restricted to 1 or 0.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.Feature}. This change is pending GeoAPI revision.</div>
+     *
      * @return the associated feature (may be {@code null}).
-     * @throws MultiValuedPropertyException if this association contains more than one value.
+     * @throws IllegalStateException if this association contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract Feature getValue() throws MultiValuedPropertyException;
+    public abstract AbstractFeature getValue() throws IllegalStateException;
 
     /**
      * Returns all features, or an empty collection if none.
@@ -151,13 +148,16 @@
      * @return the features in a <cite>live</cite> collection.
      */
     @Override
-    public Collection<Feature> getValues() {
+    public Collection<AbstractFeature> getValues() {
         return super.getValues();
     }
 
     /**
      * Sets the associated feature.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the argument type may be changed
+     * to {@code org.opengis.feature.Feature}. This change is pending GeoAPI revision.</div>
+     *
      * <h4>Validation</h4>
      * The amount of validation performed by this method is implementation dependent.
      * Usually, only the most basic constraints are verified. This is so for performance reasons
@@ -165,24 +165,24 @@
      * A more exhaustive verification can be performed by invoking the {@link #quality()} method.
      *
      * @param  value  the new value, or {@code null}.
-     * @throws InvalidPropertyValueException if the given feature is not valid for this association.
+     * @throws IllegalArgumentException if the given feature is not valid for this association.
      *
      * @see AbstractFeature#setPropertyValue(String, Object)
      */
     @Override
-    public abstract void setValue(final Feature value) throws InvalidPropertyValueException;
+    public abstract void setValue(final AbstractFeature value) throws IllegalArgumentException;
 
     /**
      * Sets the features. All previous values are replaced by the given collection.
      *
      * <p>The default implementation ensures that the given collection contains at most one element,
-     * then delegates to {@link #setValue(Feature)}.</p>
+     * then delegates to {@link #setValue(AbstractFeature)}.</p>
      *
      * @param  values  the new values.
-     * @throws InvalidPropertyValueException if the given collection contains too many elements.
+     * @throws IllegalArgumentException if the given collection contains too many elements.
      */
     @Override
-    public void setValues(final Collection<? extends Feature> values) throws InvalidPropertyValueException {
+    public void setValues(final Collection<? extends AbstractFeature> values) throws IllegalArgumentException {
         super.setValues(values);
     }
 
@@ -190,9 +190,9 @@
      * Ensures that storing a feature of the given type is valid for an association
      * expecting the given base type.
      */
-    final void ensureValid(final FeatureType base, final FeatureType type) {
+    final void ensureValid(final DefaultFeatureType base, final DefaultFeatureType type) {
         if (base != type && !DefaultFeatureType.maybeAssignableFrom(base, type)) {
-            throw new InvalidPropertyValueException(
+            throw new IllegalArgumentException(
                     Resources.format(Resources.Keys.IllegalFeatureType_3, getName(), base.getName(), type.getName()));
         }
     }
@@ -227,7 +227,7 @@
     @Override
     public String toString() {
         final String pt = DefaultAssociationRole.getTitleProperty(role);
-        final Iterator<Feature> it = getValues().iterator();
+        final Iterator<AbstractFeature> it = getValues().iterator();
         return FieldType.toString(isDeprecated(role), "FeatureAssociation", role.getName(),
                 DefaultAssociationRole.getValueTypeName(role), new Iterator<Object>()
         {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
index 83f1007..86edfcc 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
@@ -31,12 +31,6 @@
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ArgumentChecks;
 
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.MultiValuedPropertyException;
-
 
 /**
  * An instance of an {@linkplain DefaultAttributeType attribute type} containing the value of an attribute in a feature.
@@ -78,7 +72,7 @@
  * @since 0.5
  * @module
  */
-public abstract class AbstractAttribute<V> extends Field<V> implements Attribute<V>, Serializable {
+public abstract class AbstractAttribute<V> extends Field<V> implements Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -87,7 +81,7 @@
     /**
      * Information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    final AttributeType<V> type;
+    final DefaultAttributeType<V> type;
 
     /**
      * Other attributes that describes this attribute, or {@code null} if not yet created.
@@ -102,16 +96,16 @@
      *
      * @see #characteristics()
      */
-    private transient Map<String,Attribute<?>> characteristics;
+    private transient Map<String,AbstractAttribute<?>> characteristics;
 
     /**
      * Creates a new attribute of the given type.
      *
      * @param type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      *
-     * @see #create(AttributeType)
+     * @see #create(DefaultAttributeType)
      */
-    protected AbstractAttribute(final AttributeType<V> type) {
+    protected AbstractAttribute(final DefaultAttributeType<V> type) {
         this.type = type;
     }
 
@@ -125,7 +119,7 @@
      *
      * @see DefaultAttributeType#newInstance()
      */
-    public static <V> AbstractAttribute<V> create(final AttributeType<V> type) {
+    public static <V> AbstractAttribute<V> create(final DefaultAttributeType<V> type) {
         ArgumentChecks.ensureNonNull("type", type);
         return isSingleton(type.getMaximumOccurs())
                ? new SingletonAttribute<>(type)
@@ -141,7 +135,7 @@
      * @param  value  the initial value (may be {@code null}).
      * @return the new attribute.
      */
-    static <V> AbstractAttribute<V> create(final AttributeType<V> type, final Object value) {
+    static <V> AbstractAttribute<V> create(final DefaultAttributeType<V> type, final Object value) {
         ArgumentChecks.ensureNonNull("type", type);
         return isSingleton(type.getMaximumOccurs())
                ? new SingletonAttribute<>(type, value)
@@ -156,9 +150,9 @@
      */
     private void writeObject(final ObjectOutputStream out) throws IOException {
         out.defaultWriteObject();
-        final Attribute<?>[] characterizedBy;
+        final AbstractAttribute<?>[] characterizedBy;
         if (characteristics instanceof CharacteristicMap) {
-            characterizedBy = characteristics.values().toArray(new Attribute<?>[characteristics.size()]);
+            characterizedBy = characteristics.values().toArray(new AbstractAttribute<?>[characteristics.size()]);
         } else {
             characterizedBy = null;
         }
@@ -175,7 +169,7 @@
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
         try {
-            final Attribute<?>[] characterizedBy = (Attribute<?>[]) in.readObject();
+            final AbstractAttribute<?>[] characterizedBy = (AbstractAttribute<?>[]) in.readObject();
             if (characterizedBy != null) {
                 characteristics = newCharacteristicsMap();
                 characteristics.values().addAll(Arrays.asList(characterizedBy));
@@ -188,7 +182,7 @@
 
     /**
      * Returns the name of this attribute as defined by its {@linkplain #getType() type}.
-     * This convenience method delegates to {@link AttributeType#getName()}.
+     * This convenience method delegates to {@link DefaultAttributeType#getName()}.
      *
      * @return the attribute name specified by its type.
      */
@@ -200,10 +194,12 @@
     /**
      * Returns information about the attribute (base Java class, domain of values, <i>etc.</i>).
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.AttributeType}. This change is pending GeoAPI revision.</div>
+     *
      * @return information about the attribute.
      */
-    @Override
-    public AttributeType<V> getType() {
+    public DefaultAttributeType<V> getType() {
         return type;
     }
 
@@ -213,12 +209,12 @@
      * of attribute values is restricted to 1 or 0.
      *
      * @return the attribute value (may be {@code null}).
-     * @throws MultiValuedPropertyException if this attribute contains more than one value.
+     * @throws IllegalStateException if this attribute contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract V getValue() throws MultiValuedPropertyException;
+    public abstract V getValue() throws IllegalStateException;
 
     /**
      * Returns all attribute values, or an empty collection if none.
@@ -245,13 +241,13 @@
      * A more exhaustive verification can be performed by invoking the {@link #quality()} method.
      *
      * @param  value  the new value, or {@code null} for removing all values from this attribute.
-     * @throws InvalidPropertyValueException if this method verifies argument validity and the given value
+     * @throws IllegalArgumentException if this method verifies argument validity and the given value
      *         does not met the attribute constraints.
      *
      * @see AbstractFeature#setPropertyValue(String, Object)
      */
     @Override
-    public abstract void setValue(final V value) throws InvalidPropertyValueException;
+    public abstract void setValue(final V value) throws IllegalArgumentException;
 
     /**
      * Sets the attribute values. All previous values are replaced by the given collection.
@@ -260,10 +256,10 @@
      * then delegates to {@link #setValue(Object)}.</p>
      *
      * @param  values  the new values.
-     * @throws InvalidPropertyValueException if the given collection contains too many elements.
+     * @throws IllegalArgumentException if the given collection contains too many elements.
      */
     @Override
-    public void setValues(final Collection<? extends V> values) throws InvalidPropertyValueException {
+    public void setValues(final Collection<? extends V> values) throws IllegalArgumentException {
         super.setValues(values);
     }
 
@@ -342,9 +338,8 @@
      *
      * @see DefaultAttributeType#characteristics()
      */
-    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Map<String,Attribute<?>> characteristics() {
+    public Map<String,AbstractAttribute<?>> characteristics() {
         if (characteristics == null) {
             characteristics = newCharacteristicsMap();
         }
@@ -356,13 +351,13 @@
      * This method does not store the new map in the {@link #characteristics} field;
      * it is caller responsibility to do so if desired.
      */
-    private Map<String,Attribute<?>> newCharacteristicsMap() {
+    private Map<String,AbstractAttribute<?>> newCharacteristicsMap() {
         if (type instanceof DefaultAttributeType<?>) {
-            Map<String, AttributeType<?>> map = ((DefaultAttributeType<?>) type).characteristics();
+            Map<String, DefaultAttributeType<?>> map = type.characteristics();
             if (!map.isEmpty()) {
                 if (!(map instanceof CharacteristicTypeMap)) {
-                    final Collection<AttributeType<?>> types = map.values();
-                    map = CharacteristicTypeMap.create(type, types.toArray(new AttributeType<?>[types.size()]));
+                    final Collection<DefaultAttributeType<?>> types = map.values();
+                    map = CharacteristicTypeMap.create(type, types.toArray(new DefaultAttributeType<?>[types.size()]));
                 }
                 return new CharacteristicMap(this, (CharacteristicTypeMap) map);
             }
@@ -375,7 +370,7 @@
      * Contrarily to {@link #characteristics()}, this method does not create the map. This method
      * is suitable when then caller only wants to read the map and does not plan to write anything.
      */
-    final Map<String,Attribute<?>> characteristicsReadOnly() {
+    final Map<String,AbstractAttribute<?>> characteristicsReadOnly() {
         return (characteristics != null) ? characteristics : Collections.emptyMap();
     }
 
@@ -472,7 +467,7 @@
         if (characteristics != null && !characteristics.isEmpty()) {
             buffer.append(System.lineSeparator());
             String separator = "└─ characteristics: ";
-            for (final Map.Entry<String,Attribute<?>> entry : characteristics.entrySet()) {
+            for (final Map.Entry<String,AbstractAttribute<?>> entry : characteristics.entrySet()) {
                 buffer.append(separator).append(entry.getKey()).append('=').append(entry.getValue().getValue());
                 separator = ", ";
             }
@@ -497,7 +492,7 @@
     @SuppressWarnings({"CloneInNonCloneableClass", "unchecked"})
     public AbstractAttribute<V> clone() throws CloneNotSupportedException {
         final AbstractAttribute<V> clone = (AbstractAttribute<V>) super.clone();
-        final Map<String,Attribute<?>> c = clone.characteristics;
+        final Map<String,AbstractAttribute<?>> c = clone.characteristics;
         if (c instanceof CharacteristicMap) {
             clone.characteristics = ((CharacteristicMap) c).clone();
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
index 3afed12..3dbd0e5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
@@ -33,20 +33,6 @@
 import org.apache.sis.internal.util.CheckedArrayList;
 import org.apache.sis.internal.feature.Resources;
 
-// Branch-dependent imports
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-
 
 /**
  * An instance of a {@linkplain DefaultFeatureType feature type} containing values for a real-world phenomena.
@@ -86,7 +72,7 @@
  * @since 0.5
  * @module
  */
-public abstract class AbstractFeature implements Feature, Serializable {
+public abstract class AbstractFeature implements Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -102,7 +88,7 @@
     /**
      * Information about the feature (name, characteristics, <i>etc.</i>).
      */
-    final FeatureType type;
+    final DefaultFeatureType type;
 
     /**
      * Creates a new feature of the given type.
@@ -111,7 +97,7 @@
      *
      * @see DefaultFeatureType#newInstance()
      */
-    protected AbstractFeature(final FeatureType type) {
+    protected AbstractFeature(final DefaultFeatureType type) {
         ArgumentChecks.ensureNonNull("type", type);
         this.type = type;
     }
@@ -127,10 +113,12 @@
     /**
      * Returns information about the feature (name, characteristics, <i>etc.</i>).
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.FeatureType}. This change is pending GeoAPI revision.</div>
+     *
      * @return information about the feature.
      */
-    @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -155,15 +143,17 @@
      * Implementers are encouraged to override this method if they can provide a more efficient implementation.
      * Note that this is already the case when using implementations created by {@link DefaultFeatureType#newInstance()}.</div>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.Property}. This change is pending GeoAPI revision.</div>
+     *
      * @param  name  the property name.
      * @return the property of the given name (never {@code null}).
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      *
      * @see #getPropertyValue(String)
      * @see DefaultFeatureType#getProperty(String)
      */
-    @Override
-    public Property getProperty(final String name) throws PropertyNotFoundException {
+    public Object getProperty(final String name) throws IllegalArgumentException {
         return PropertyView.create(this, type.getProperty(name));
     }
 
@@ -198,22 +188,24 @@
      * Implementers are encouraged to override this method if they can provide a better implementation.
      * Note that this is already the case when using implementations created by {@link DefaultFeatureType#newInstance()}.</div>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the argument may be changed
+     * to {@code org.opengis.feature.Property}. This change is pending GeoAPI revision.</div>
+     *
      * @param  property  the property to set.
-     * @throws PropertyNotFoundException if the name of the given property is not a property name of this feature.
-     * @throws InvalidPropertyValueException if the value of the given property is not valid.
+     * @throws IllegalArgumentException if the name of the given property is not a property name of this feature.
+     * @throws IllegalArgumentException if the value of the given property is not valid.
      * @throws IllegalArgumentException if the property can not be set for another reason.
      *
      * @see #setPropertyValue(String, Object)
      */
-    @Override
-    public void setProperty(final Property property) throws IllegalArgumentException {
+    public void setProperty(final Object property) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("property", property);
-        final String name = property.getName().toString();
-        verifyPropertyType(name, property);
-        if (property instanceof Attribute<?> && !Containers.isNullOrEmpty(((Attribute<?>) property).characteristics())) {
+        final String name = ((Property) property).getName().toString();
+        verifyPropertyType(name, (Property) property);
+        if (property instanceof AbstractAttribute<?> && !Containers.isNullOrEmpty(((AbstractAttribute<?>) property).characteristics())) {
             throw new IllegalArgumentException(Resources.format(Resources.Keys.CanNotAssignCharacteristics_1, name));
         }
-        setPropertyValue(name, property.getValue());
+        setPropertyValue(name, ((Property) property).getValue());
     }
 
     /**
@@ -225,11 +217,11 @@
      * @return a {@code Property} wrapping the given value.
      */
     final Property createProperty(final String name, final Object value) {
-        final PropertyType pt = type.getProperty(name);
-        if (pt instanceof AttributeType<?>) {
-            return AbstractAttribute.create((AttributeType<?>) pt, value);
-        } else if (pt instanceof FeatureAssociationRole) {
-            return AbstractAssociation.create((FeatureAssociationRole) pt, value);
+        final AbstractIdentifiedType pt = type.getProperty(name);
+        if (pt instanceof DefaultAttributeType<?>) {
+            return AbstractAttribute.create((DefaultAttributeType<?>) pt, value);
+        } else if (pt instanceof DefaultAssociationRole) {
+            return AbstractAssociation.create((DefaultAssociationRole) pt, value);
         } else {
             // Should never happen, unless the user gave us some mutable FeatureType.
             throw new CorruptedObjectException(Errors.format(Errors.Keys.UnknownType_1, pt));
@@ -241,15 +233,15 @@
      *
      * @param  name  the name of the property to create.
      * @return a {@code Property} of the given name.
-     * @throws PropertyNotFoundException if the given argument is not the name of an attribute or
+     * @throws IllegalArgumentException if the given argument is not the name of an attribute or
      *         feature association of this feature.
      */
-    final Property createProperty(final String name) throws PropertyNotFoundException {
-        final PropertyType pt = type.getProperty(name);
-        if (pt instanceof AttributeType<?>) {
-            return ((AttributeType<?>) pt).newInstance();
-        } else if (pt instanceof FeatureAssociationRole) {
-            return ((FeatureAssociationRole) pt).newInstance();
+    final Property createProperty(final String name) throws IllegalArgumentException {
+        final AbstractIdentifiedType pt = type.getProperty(name);
+        if (pt instanceof DefaultAttributeType<?>) {
+            return ((DefaultAttributeType<?>) pt).newInstance();
+        } else if (pt instanceof DefaultAssociationRole) {
+            return ((DefaultAssociationRole) pt).newInstance();
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
         }
@@ -260,14 +252,14 @@
      *
      * @see #getOperationValue(String)
      */
-    final Property getOperationResult(final String name) {
+    final Object getOperationResult(final String name) {
         /*
          * The (Operation) cast below should never fail (unless the DefaultFeatureType in not really immutable,
          * which would be a contract violation) because all callers shall ensure that this method is invoked in
          * a context where the following assertion holds.
          */
-        assert DefaultFeatureType.OPERATION_INDEX.equals(((DefaultFeatureType) type).indices().get(name)) : name;
-        return ((Operation) type.getProperty(name)).apply(this, null);
+        assert DefaultFeatureType.OPERATION_INDEX.equals(type.indices().get(name)) : name;
+        return ((AbstractOperation) type.getProperty(name)).apply(this, null);
     }
 
     /**
@@ -276,14 +268,14 @@
      *
      * @param  name  the name of the property for which to get the default value.
      * @return the default value for the {@code Property} of the given name.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      */
-    final Object getDefaultValue(final String name) throws PropertyNotFoundException {
-        final PropertyType pt = type.getProperty(name);
-        if (pt instanceof AttributeType<?>) {
-            return getDefaultValue((AttributeType<?>) pt);
-        } else if (pt instanceof FeatureAssociationRole) {
-            final int maximumOccurs = ((FeatureAssociationRole) pt).getMaximumOccurs();
+    final Object getDefaultValue(final String name) throws IllegalArgumentException {
+        final AbstractIdentifiedType pt = type.getProperty(name);
+        if (pt instanceof DefaultAttributeType<?>) {
+            return getDefaultValue((DefaultAttributeType<?>) pt);
+        } else if (pt instanceof DefaultAssociationRole) {
+            final int maximumOccurs = ((DefaultAssociationRole) pt).getMaximumOccurs();
             return maximumOccurs > 1 ? Collections.EMPTY_LIST : null;       // No default value for associations.
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
@@ -293,7 +285,7 @@
     /**
      * Returns the default value to be returned by {@link #getPropertyValue(String)} for the given attribute type.
      */
-    private static <V> Object getDefaultValue(final AttributeType<V> attribute) {
+    private static <V> Object getDefaultValue(final DefaultAttributeType<V> attribute) {
         final V defaultValue = attribute.getDefaultValue();
         if (Field.isSingleton(attribute.getMaximumOccurs())) {
             return defaultValue;
@@ -311,10 +303,10 @@
      * <table class="sis">
      *   <caption>Class of returned value</caption>
      *   <tr><th>Property type</th>                  <th>max. occurs</th> <th>Method invoked</th>                         <th>Return type</th></tr>
-     *   <tr><td>{@link AttributeType}</td>          <td>0 or 1</td>      <td>{@link Attribute#getValue()}</td>           <td>{@link Object}</td></tr>
-     *   <tr><td>{@code AttributeType}</td>          <td>2 or more</td>   <td>{@link Attribute#getValues()}</td>          <td>{@code Collection<?>}</td></tr>
-     *   <tr><td>{@link FeatureAssociationRole}</td> <td>0 or 1</td>      <td>{@link FeatureAssociation#getValue()}</td>  <td>{@link Feature}</td></tr>
-     *   <tr><td>{@code FeatureAssociationRole}</td> <td>2 or more</td>   <td>{@link FeatureAssociation#getValues()}</td> <td>{@code Collection<Feature>}</td></tr>
+     *   <tr><td>{@code AttributeType}</td>          <td>0 or 1</td>      <td>{@code Attribute.getValue()}</td>           <td>{@link Object}</td></tr>
+     *   <tr><td>{@code AttributeType}</td>          <td>2 or more</td>   <td>{@code Attribute.getValues()}</td>          <td>{@code Collection<?>}</td></tr>
+     *   <tr><td>{@code FeatureAssociationRole}</td> <td>0 or 1</td>      <td>{@code FeatureAssociation.getValue()}</td>  <td>{@code Feature}</td></tr>
+     *   <tr><td>{@code FeatureAssociationRole}</td> <td>2 or more</td>   <td>{@code FeatureAssociation.getValues()}</td> <td>{@code Collection<Feature>}</td></tr>
      * </table>
      *
      * <div class="note"><b>Note:</b> “max. occurs” is the {@linkplain DefaultAttributeType#getMaximumOccurs() maximum
@@ -336,12 +328,11 @@
      * @param  name  the property name.
      * @return value of the specified property, or the
      *         {@linkplain DefaultAttributeType#getDefaultValue() default value} (which may be {@code null}} if none.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      *
      * @see AbstractAttribute#getValue()
      */
-    @Override
-    public abstract Object getPropertyValue(final String name) throws PropertyNotFoundException;
+    public abstract Object getPropertyValue(final String name) throws IllegalArgumentException;
 
     /**
      * Sets the value for the property of the given name.
@@ -354,13 +345,12 @@
      *
      * @param  name   the attribute name.
      * @param  value  the new value for the given attribute (may be {@code null}).
-     * @throws PropertyNotFoundException if the given name is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given name is not an attribute or association name of this feature.
      * @throws ClassCastException if the value is not assignable to the expected value class.
-     * @throws InvalidPropertyValueException if the given value is not valid for a reason other than its type.
+     * @throws IllegalArgumentException if the given value is not valid for a reason other than its type.
      *
      * @see AbstractAttribute#setValue(Object)
      */
-    @Override
     public abstract void setPropertyValue(final String name, final Object value) throws IllegalArgumentException;
 
     /**
@@ -387,7 +377,6 @@
      *
      * @since 1.1
      */
-    @Override
     public abstract Object getValueOrFallback(final String name, Object missingPropertyFallback);
 
     /**
@@ -410,21 +399,21 @@
      * }
      *
      * @param  name  the name of the operation to execute. The caller is responsible to ensure that the
-     *               property type for that name is an instance of {@link Operation}.
+     *               property type for that name is an instance of {@link AbstractOperation}.
      * @return the result value of the given operation, or {@code null} if none.
      *
      * @since 0.8
      */
     protected Object getOperationValue(final String name) {
-        final Operation operation = (Operation) type.getProperty(name);
+        final AbstractOperation operation = (AbstractOperation) type.getProperty(name);
         if (operation instanceof LinkOperation) {
             return getPropertyValue(((LinkOperation) operation).referentName);
         }
-        final Property result = operation.apply(this, null);
-        if (result instanceof Attribute<?>) {
-            return getAttributeValue((Attribute<?>) result);
-        } else if (result instanceof FeatureAssociation) {
-            return getAssociationValue((FeatureAssociation) result);
+        final Object result = operation.apply(this, null);
+        if (result instanceof AbstractAttribute<?>) {
+            return getAttributeValue((AbstractAttribute<?>) result);
+        } else if (result instanceof AbstractAssociation) {
+            return getAssociationValue((AbstractAssociation) result);
         } else {
             return null;
         }
@@ -437,20 +426,20 @@
      * but the {@linkplain FeatureOperations#link link} operation for instance does.
      *
      * @param  name   the name of the operation to execute. The caller is responsible to ensure that the
-     *                property type for that name is an instance of {@link Operation}.
+     *                property type for that name is an instance of {@link AbstractOperation}.
      * @param  value  the value to assign to the result of the named operation.
      * @throws IllegalStateException if the operation of the given name does not accept assignment.
      *
      * @since 0.8
      */
     protected void setOperationValue(final String name, final Object value) {
-        final Operation operation = (Operation) type.getProperty(name);
+        final AbstractOperation operation = (AbstractOperation) type.getProperty(name);
         if (operation instanceof LinkOperation) {
             setPropertyValue(((LinkOperation) operation).referentName, value);
         } else {
-            final Property result = operation.apply(this, null);
-            if (result != null) {
-                setPropertyValue(result, value);
+            final Object result = operation.apply(this, null);
+            if (result instanceof Property) {
+                setPropertyValue((Property) result, value);
             } else {
                 throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSetPropertyValue_1, name));
             }
@@ -461,7 +450,7 @@
      * Returns the value of the given attribute, as a singleton or as a collection depending
      * on the maximum number of occurrences.
      */
-    static Object getAttributeValue(final Attribute<?> property) {
+    static Object getAttributeValue(final AbstractAttribute<?> property) {
         return Field.isSingleton(property.getType().getMaximumOccurs()) ? property.getValue() : property.getValues();
     }
 
@@ -469,7 +458,7 @@
      * Returns the value of the given association, as a singleton or as a collection depending
      * on the maximum number of occurrences.
      */
-    static Object getAssociationValue(final FeatureAssociation property) {
+    static Object getAssociationValue(final AbstractAssociation property) {
         return Field.isSingleton(property.getRole().getMaximumOccurs()) ? property.getValue() : property.getValues();
     }
 
@@ -477,10 +466,10 @@
      * Sets the value of the given property, with some minimal checks.
      */
     static void setPropertyValue(final Property property, final Object value) {
-        if (property instanceof Attribute<?>) {
-            setAttributeValue((Attribute<?>) property, value);
-        } else if (property instanceof FeatureAssociation) {
-            setAssociationValue((FeatureAssociation) property, value);
+        if (property instanceof AbstractAttribute<?>) {
+            setAttributeValue((AbstractAttribute<?>) property, value);
+        } else if (property instanceof AbstractAssociation) {
+            setAssociationValue((AbstractAssociation) property, value);
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(property.getName()));
         }
@@ -492,9 +481,9 @@
      * use {@link Validator} instead.
      */
     @SuppressWarnings("unchecked")
-    private static <V> void setAttributeValue(final Attribute<V> attribute, final Object value) {
+    private static <V> void setAttributeValue(final AbstractAttribute<V> attribute, final Object value) {
         if (value != null) {
-            final AttributeType<V> pt = attribute.getType();
+            final DefaultAttributeType<V> pt = attribute.getType();
             final Class<?> base = pt.getValueClass();
             if (!base.isInstance(value)) {
                 Object element = value;
@@ -505,7 +494,7 @@
                      */
                     final Iterator<?> it = ((Collection<?>) value).iterator();
                     do if (!it.hasNext()) {
-                        ((Attribute) attribute).setValues((Collection) value);
+                        ((AbstractAttribute) attribute).setValues((Collection) value);
                         return;
                     } while ((element = it.next()) == null || base.isInstance(element));
                     // Found an illegal value. Exeption is thrown below.
@@ -513,7 +502,7 @@
                 throw new ClassCastException(illegalValueClass(pt, base, element));         // `element` can not be null here.
             }
         }
-        ((Attribute) attribute).setValue(value);
+        ((AbstractAttribute) attribute).setValue(value);
     }
 
     /**
@@ -521,24 +510,24 @@
      * For a more exhaustive validation, use {@link Validator} instead.
      */
     @SuppressWarnings("unchecked")
-    private static void setAssociationValue(final FeatureAssociation association, final Object value) {
+    private static void setAssociationValue(final AbstractAssociation association, final Object value) {
         if (value != null) {
-            final FeatureAssociationRole role = association.getRole();
-            final FeatureType base = role.getValueType();
-            if (value instanceof Feature) {
-                final FeatureType actual = ((Feature) value).getType();
+            final DefaultAssociationRole role = association.getRole();
+            final DefaultFeatureType base = role.getValueType();
+            if (value instanceof AbstractFeature) {
+                final DefaultFeatureType actual = ((AbstractFeature) value).getType();
                 if (base != actual && !DefaultFeatureType.maybeAssignableFrom(base, actual)) {
-                    throw new InvalidPropertyValueException(illegalFeatureType(role, base, actual));
+                    throw new IllegalArgumentException(illegalFeatureType(role, base, actual));
                 }
             } else if (value instanceof Collection<?>) {
                 verifyAssociationValues(role, (Collection<?>) value);
-                association.setValues((Collection<? extends Feature>) value);
+                association.setValues((Collection<? extends AbstractFeature>) value);
                 return;                                 // Skip the setter at the end of this method.
             } else {
-                throw new ClassCastException(illegalValueClass(role, Feature.class, value));
+                throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
             }
         }
-        association.setValue((Feature) value);
+        association.setValue((AbstractFeature) value);
     }
 
     /**
@@ -556,7 +545,7 @@
             if (value == null) {
                 return true;
             }
-            if (previous.getClass() == value.getClass() && !(value instanceof Feature)) {
+            if (previous.getClass() == value.getClass() && !(value instanceof AbstractFeature)) {
                 return true;
             }
         }
@@ -570,19 +559,19 @@
      * @param  property  the property to verify.
      */
     final void verifyPropertyType(final String name, final Property property) {
-        final PropertyType pt, base = type.getProperty(name);
-        if (property instanceof Attribute<?>) {
-            pt = ((Attribute<?>) property).getType();
-        } else if (property instanceof FeatureAssociation) {
-            pt = ((FeatureAssociation) property).getRole();
+        final AbstractIdentifiedType pt, base = type.getProperty(name);
+        if (property instanceof AbstractAttribute<?>) {
+            pt = ((AbstractAttribute<?>) property).getType();
+        } else if (property instanceof AbstractAssociation) {
+            pt = ((AbstractAssociation) property).getRole();
         } else {
-            throw new InvalidPropertyValueException(Resources.format(Resources.Keys.IllegalPropertyType_2, base.getName(), property.getClass()));
+            throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalPropertyType_2, base.getName(), property.getClass()));
         }
         if (pt != base) {
             if (base == null) {
-                throw new PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
+                throw new IllegalArgumentException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
             } else {
-                throw new InvalidPropertyValueException(Resources.format(Resources.Keys.MismatchedPropertyType_1, name));
+                throw new IllegalArgumentException(Resources.format(Resources.Keys.MismatchedPropertyType_1, name));
             }
         }
     }
@@ -592,14 +581,14 @@
      * The returned value is usually the same than the given one, except in the case of collections.
      */
     final Object verifyPropertyValue(final String name, final Object value) {
-        final PropertyType pt = type.getProperty(name);
-        if (pt instanceof AttributeType<?>) {
+        final AbstractIdentifiedType pt = type.getProperty(name);
+        if (pt instanceof DefaultAttributeType<?>) {
             if (value != null) {
-                return verifyAttributeValue((AttributeType<?>) pt, value);
+                return verifyAttributeValue((DefaultAttributeType<?>) pt, value);
             }
-        } else if (pt instanceof FeatureAssociationRole) {
+        } else if (pt instanceof DefaultAssociationRole) {
             if (value != null) {
-                return verifyAssociationValue((FeatureAssociationRole) pt, value);
+                return verifyAssociationValue((DefaultAssociationRole) pt, value);
             }
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
@@ -617,7 +606,7 @@
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static <T> Object verifyAttributeValue(final AttributeType<T> type, final Object value) {
+    private static <T> Object verifyAttributeValue(final DefaultAttributeType<T> type, final Object value) {
         final Class<T> valueClass = type.getValueClass();
         final boolean isSingleton = Field.isSingleton(type.getMaximumOccurs());
         if (valueClass.isInstance(value)) {
@@ -639,42 +628,42 @@
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static Object verifyAssociationValue(final FeatureAssociationRole role, final Object value) {
+    private static Object verifyAssociationValue(final DefaultAssociationRole role, final Object value) {
         final boolean isSingleton = Field.isSingleton(role.getMaximumOccurs());
-        if (value instanceof Feature) {
+        if (value instanceof AbstractFeature) {
             /*
              * If the user gave us a single value, first verify its validity.
              * Then wrap it in a list of 1 element if this property is multi-valued.
              */
-            final FeatureType valueType = ((Feature) value).getType();
-            final FeatureType base = role.getValueType();
+            final DefaultFeatureType valueType = ((AbstractFeature) value).getType();
+            final DefaultFeatureType base = role.getValueType();
             if (base == valueType || DefaultFeatureType.maybeAssignableFrom(base, valueType)) {
-                return isSingleton ? value : singletonList(Feature.class, role.getMinimumOccurs(), value);
+                return isSingleton ? value : singletonList(AbstractFeature.class, role.getMinimumOccurs(), value);
             } else {
-                throw new InvalidPropertyValueException(illegalFeatureType(role, base, valueType));
+                throw new IllegalArgumentException(illegalFeatureType(role, base, valueType));
             }
         } else if (!isSingleton && value instanceof Collection<?>) {
             verifyAssociationValues(role, (Collection<?>) value);
-            return CheckedArrayList.castOrCopy((Collection<?>) value, Feature.class);
+            return CheckedArrayList.castOrCopy((Collection<?>) value, AbstractFeature.class);
         } else {
-            throw new ClassCastException(illegalValueClass(role, Feature.class, value));
+            throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
         }
     }
 
     /**
      * Verifies if all values in the given collection are valid instances of feature for the given association role.
      */
-    private static void verifyAssociationValues(final FeatureAssociationRole role, final Collection<?> values) {
-        final FeatureType base = role.getValueType();
+    private static void verifyAssociationValues(final DefaultAssociationRole role, final Collection<?> values) {
+        final DefaultFeatureType base = role.getValueType();
         int index = 0;
         for (final Object value : values) {
             ArgumentChecks.ensureNonNullElement("values", index, value);
-            if (!(value instanceof Feature)) {
-                throw new ClassCastException(illegalValueClass(role, Feature.class, value));
+            if (!(value instanceof AbstractFeature)) {
+                throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
             }
-            final FeatureType type = ((Feature) value).getType();
+            final DefaultFeatureType type = ((AbstractFeature) value).getType();
             if (base != type && !DefaultFeatureType.maybeAssignableFrom(base, type)) {
-                throw new InvalidPropertyValueException(illegalFeatureType(role, base, type));
+                throw new IllegalArgumentException(illegalFeatureType(role, base, type));
             }
             index++;
         }
@@ -700,7 +689,7 @@
      */
     static String propertyNotFound(final FeatureType type, final Object feature, final String property) {
         GenericName ambiguous = null;
-        for (final IdentifiedType p : type.getProperties(true)) {
+        for (final AbstractIdentifiedType p : type.getProperties(true)) {
             final GenericName next = p.getName();
             GenericName name = next;
             do {
@@ -718,7 +707,7 @@
 
     /**
      * Returns the exception message for a property type which is neither an attribute or an association.
-     * This method is invoked after a {@link PropertyType} has been found for the user-supplied name,
+     * This method is invoked after a {@code PropertyType} has been found for the user-supplied name,
      * but that property can not be stored in or extracted from a {@link Property} instance.
      */
     static String unsupportedPropertyType(final GenericName name) {
@@ -730,7 +719,7 @@
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static String illegalValueClass(final IdentifiedType property, final Class<?> expected, final Object value) {
+    private static String illegalValueClass(final AbstractIdentifiedType property, final Class<?> expected, final Object value) {
         return Resources.format(Resources.Keys.IllegalPropertyValueClass_3,
                                 property.getName(), expected, value.getClass());
     }
@@ -739,7 +728,7 @@
      * Returns the exception message for an association value of wrong type.
      */
     private static String illegalFeatureType(
-            final FeatureAssociationRole association, final FeatureType expected, final FeatureType actual)
+            final DefaultAssociationRole association, final FeatureType expected, final FeatureType actual)
     {
         return Resources.format(Resources.Keys.IllegalFeatureType_3,
                                 association.getName(), expected.getName(), actual.getName());
@@ -871,7 +860,7 @@
     public int hashCode() {
         int code = type.hashCode() * 37;
         if (comparisonStart()) try {
-            for (final PropertyType pt : type.getProperties(true)) {
+            for (final AbstractIdentifiedType pt : type.getProperties(true)) {
                 final String name = pt.getName().toString();
                 if (name != null) {                                             // Paranoiac check.
                     final Object value = getPropertyValue(name);
@@ -919,7 +908,7 @@
                 return false;
             }
             if (comparisonStart()) try {
-                for (final PropertyType pt : type.getProperties(true)) {
+                for (final AbstractIdentifiedType pt : type.getProperties(true)) {
                     final String name = pt.getName().toString();
                     if (!Objects.equals(getPropertyValue(name), that.getPropertyValue(name))) {
                         return false;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
index 608d704..217cc3d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
@@ -30,19 +30,21 @@
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 
-// Branch-dependent imports
-import org.opengis.feature.IdentifiedType;
-
 
 /**
  * Identification and description information inherited by property types and feature types.
  *
+ * <div class="warning"><b>Warning:</b>
+ * This class is expected to implement a GeoAPI {@code IdentifiedType} interface in a future version.
+ * When such interface will be available, most references to {@code AbstractIdentifiedType} in the API
+ * will be replaced by references to the {@code IdentifiedType} interface.</div>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  * @since   0.5
  * @module
  */
-public class AbstractIdentifiedType implements IdentifiedType, Deprecable, Serializable {
+public class AbstractIdentifiedType implements Deprecable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -254,7 +256,6 @@
      *
      * @return the type name.
      */
-    @Override
     public final GenericName getName() {
         return name;
     }
@@ -264,7 +265,6 @@
      *
      * @return concise definition of the element.
      */
-    @Override
     public InternationalString getDefinition() {
         return definition;
     }
@@ -275,7 +275,6 @@
      *
      * @return natural language designator for the element, or {@code null} if none.
      */
-    @Override
     public InternationalString getDesignation() {
         return designation;
     }
@@ -289,7 +288,6 @@
      *
      * @return information beyond that required for concise definition of the element, or {@code null} if none.
      */
-    @Override
     public InternationalString getDescription() {
         return description;
     }
@@ -379,7 +377,9 @@
      * @param  index      index of the characteristics having the given name.
      * @throws IllegalArgumentException if the given name is null or have an empty string representation.
      */
-    static String toString(final GenericName name, final IdentifiedType container, final String argument, final int index) {
+    static String toString(final GenericName name, final AbstractIdentifiedType container,
+            final String argument, final int index)
+    {
         short key = Errors.Keys.MissingValueForProperty_1;
         if (name != null) {
             final String s = name.toString();
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java
index fa35d1a..52ec3d5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java
@@ -32,14 +32,6 @@
 import org.apache.sis.util.Classes;
 
 // Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureOperationException;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.Property;
 
 
 /**
@@ -54,8 +46,8 @@
  * <div class="note"><b>Example:</b> a mutator operation may raise the height of a dam. This changes
  * may affect other properties like the watercourse and the reservoir associated with the dam.</div>
  *
- * The value is computed, or the operation is executed, by {@link #apply(Feature, ParameterValueGroup)}.
- * If the value is modifiable, new value can be set by call to {@link Attribute#setValue(Object)}.
+ * The value is computed, or the operation is executed, by {@code apply(Feature, ParameterValueGroup)}.
+ * If the value is modifiable, new value can be set by call to {@code Attribute.setValue(Object)}.
  *
  * <div class="warning"><b>Warning:</b> this class is experimental and may change after we gained more
  * experience on this aspect of ISO 19109.</div>
@@ -68,8 +60,8 @@
  * @since 0.6
  * @module
  */
-public abstract class AbstractOperation extends AbstractIdentifiedType implements Operation,
-        BiFunction<Feature, ParameterValueGroup, Property>
+public abstract class AbstractOperation extends AbstractIdentifiedType
+        implements BiFunction<AbstractFeature, ParameterValueGroup, Object>
 {
     /**
      * For cross-version compatibility.
@@ -157,16 +149,17 @@
      *
      * @return description of the input parameters.
      */
-    @Override
     public abstract ParameterDescriptorGroup getParameters();
 
     /**
      * Returns the expected result type, or {@code null} if none.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.IdentifiedType}. This change is pending GeoAPI revision.</div>
+     *
      * @return the type of the result, or {@code null} if none.
      */
-    @Override
-    public abstract IdentifiedType getResult();
+    public abstract AbstractIdentifiedType getResult();
 
     /**
      * Executes the operation on the specified feature with the specified parameters.
@@ -175,11 +168,11 @@
      * <ul>
      *   <li>If {@code getResult()} returns {@code null},
      *       then this method should return {@code null}.</li>
-     *   <li>If {@code getResult()} returns an instance of {@link AttributeType},
-     *       then this method shall return an instance of {@link Attribute}
+     *   <li>If {@code getResult()} returns an instance of {@code AttributeType},
+     *       then this method shall return an instance of {@code Attribute}
      *       and the {@code Attribute.getType() == getResult()} relation should hold.</li>
-     *   <li>If {@code getResult()} returns an instance of {@link org.opengis.feature.FeatureAssociationRole},
-     *       then this method shall return an instance of {@link FeatureAssociation}
+     *   <li>If {@code getResult()} returns an instance of {@code FeatureAssociationRole},
+     *       then this method shall return an instance of {@code FeatureAssociation}
      *       and the {@code FeatureAssociation.getRole() == getResult()} relation should hold.</li>
      * </ul>
      *
@@ -189,15 +182,18 @@
      * in the Java language, and may be {@code null} if the operation does not need a feature instance
      * (like static methods in the Java language).</div>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the parameter type and return value may
+     * be changed to {@code org.opengis.feature.Feature} and {@code org.opengis.feature.Property} respectively.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param  feature     the feature on which to execute the operation.
      *                     Can be {@code null} if the operation does not need feature instance.
      * @param  parameters  the parameters to use for executing the operation.
      *                     Can be {@code null} if the operation does not take any parameters.
      * @return the operation result, or {@code null} if this operation does not produce any result.
-     * @throws FeatureOperationException if the operation execution can not complete.
      */
     @Override
-    public abstract Property apply(Feature feature, ParameterValueGroup parameters) throws FeatureOperationException;
+    public abstract Object apply(AbstractFeature feature, ParameterValueGroup parameters);
 
     /**
      * Returns the names of feature properties that this operation needs for performing its task.
@@ -268,11 +264,11 @@
         if (name != null) {
             buffer.append('”');
         }
-        final IdentifiedType result = getResult();
+        final AbstractIdentifiedType result = getResult();
         if (result != null) {
             final Object type;
-            if (result instanceof AttributeType<?>) {
-                type = Classes.getShortName(((AttributeType<?>) result).getValueClass());
+            if (result instanceof DefaultAttributeType<?>) {
+                type = Classes.getShortName(((DefaultAttributeType<?>) result).getValueClass());
             } else {
                 type = result.getName();
             }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AssociationView.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AssociationView.java
index 529bed1..a97ac0e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AssociationView.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AssociationView.java
@@ -20,9 +20,7 @@
 import org.opengis.util.GenericName;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
+import java.util.Objects;
 
 
 /**
@@ -39,23 +37,30 @@
  * @since   0.8
  * @module
  */
-class AssociationView extends PropertyView<Feature> implements FeatureAssociation {
+class AssociationView extends AbstractAssociation {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = -148100967531766909L;
 
     /**
-     * The role of this association. Must be one of the properties listed in the {@link #feature}.
+     * The feature from which to read and where to write the attribute or association value.
      */
-    private final FeatureAssociationRole role;
+    final AbstractFeature feature;
+
+    /**
+     * The string representation of the property name. This is the value to be given in calls to
+     * {@code Feature.getPropertyValue(String)} and {@code Feature.setPropertyValue(String, Object)}.
+     */
+    final String name;
 
     /**
      * Creates a new association which will delegate its work to the given feature.
      */
-    private AssociationView(final Feature feature, final FeatureAssociationRole role) {
-        super(feature, role.getName().toString());
-        this.role = role;
+    private AssociationView(final AbstractFeature feature, final DefaultAssociationRole role) {
+        super(role);
+        this.feature = feature;
+        this.name = role.getName().toString();
     }
 
     /**
@@ -65,7 +70,7 @@
      * @param role     the role of this association. Must be one of the properties listed in the
      *                 {@link #feature} (this is not verified by this constructor).
      */
-    static FeatureAssociation create(final Feature feature, final FeatureAssociationRole role) {
+    static AbstractAssociation create(final AbstractFeature feature, final DefaultAssociationRole role) {
         if (isSingleton(role.getMaximumOccurs())) {
             return new Singleton(feature, role);
         } else {
@@ -81,20 +86,24 @@
         return role.getName();
     }
 
-    /**
-     * Returns the role specified at construction time.
-     */
     @Override
-    public final FeatureAssociationRole getRole() {
-        return role;
+    public AbstractFeature getValue() {
+        return (AbstractFeature) PropertyView.getValue(feature, name);
     }
 
-    /**
-     * Returns the class of values.
-     */
     @Override
-    final Class<Feature> getValueClass() {
-        return Feature.class;
+    public void setValue(final AbstractFeature value) {
+        PropertyView.setValue(feature, name, value);
+    }
+
+    @Override
+    public Collection<AbstractFeature> getValues() {
+        return PropertyView.getValues(feature, name, AbstractFeature.class);
+    }
+
+    @Override
+    public final void setValues(final Collection<? extends AbstractFeature> values) {
+        PropertyView.setValues(feature, name, values);
     }
 
     /**
@@ -111,7 +120,7 @@
         /**
          * Creates a new association which will delegate its work to the given feature.
          */
-        Singleton(final Feature feature, final FeatureAssociationRole role) {
+        Singleton(final AbstractFeature feature, final DefaultAssociationRole role) {
             super(feature, role);
         }
 
@@ -119,17 +128,17 @@
          * Returns the single value, or {@code null} if none.
          */
         @Override
-        public Feature getValue() {
-            return (Feature) this.feature.getPropertyValue(this.name);
+        public AbstractFeature getValue() {
+            return (AbstractFeature) this.feature.getPropertyValue(this.name);
         }
 
         /**
          * Sets the value of this association. This method assumes that the
-         * {@link Feature#setPropertyValue(String, Object)} implementation
+         * {@code Feature.setPropertyValue(String, Object)} implementation
          * will verify the argument type.
          */
         @Override
-        public void setValue(final Feature value) {
+        public void setValue(final AbstractFeature value) {
             this.feature.setPropertyValue(this.name, value);
         }
 
@@ -137,8 +146,30 @@
          * Wraps the property value in a set.
          */
         @Override
-        public Collection<Feature> getValues() {
-            return singletonOrEmpty(getValue());
+        public Collection<AbstractFeature> getValues() {
+            return PropertyView.singletonOrEmpty(getValue());
         }
     }
+
+    @Override
+    public final int hashCode() {
+        return PropertyView.hashCode(feature, name);
+    }
+
+    @Override
+    public final boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj != null && obj.getClass() == getClass()) {
+            final AssociationView that = (AssociationView) obj;
+            return feature == that.feature && Objects.equals(name, that.name);
+        }
+        return false;
+    }
+
+    @Override
+    public final String toString() {
+        return PropertyView.toString(getClass(), AbstractFeature.class, getName(), getValues());
+    }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AttributeView.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AttributeView.java
index 613c30e..c93e657 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AttributeView.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AttributeView.java
@@ -22,9 +22,7 @@
 import org.opengis.util.GenericName;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
+import java.util.Objects;
 
 
 /**
@@ -41,23 +39,30 @@
  * @since   0.8
  * @module
  */
-class AttributeView<V> extends PropertyView<V> implements Attribute<V> {
+class AttributeView<V> extends AbstractAttribute<V> {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = 3617999929561826634L;
 
     /**
-     * The type of this attribute. Must be one of the properties listed in the {@link #feature}.
+     * The feature from which to read and where to write the attribute or association value.
      */
-    final AttributeType<V> type;
+    final AbstractFeature feature;
+
+    /**
+     * The string representation of the property name. This is the value to be given in calls to
+     * {@code Feature.getPropertyValue(String)} and {@code Feature.setPropertyValue(String, Object)}.
+     */
+    final String name;
 
     /**
      * Creates a new attribute which will delegate its work to the given feature.
      */
-    private AttributeView(final Feature feature, final AttributeType<V> type) {
-        super(feature, type.getName().toString());
-        this.type = type;
+    private AttributeView(final AbstractFeature feature, final DefaultAttributeType<V> type) {
+        super(type);
+        this.feature = feature;
+        this.name = type.getName().toString();
     }
 
     /**
@@ -67,7 +72,7 @@
      * @param type     the type of this attribute. Must be one of the properties listed in the
      *                 {@link #feature} (this is not verified by this constructor).
      */
-    static <V> Attribute<V> create(final Feature feature, final AttributeType<V> type) {
+    static <V> AbstractAttribute<V> create(final AbstractFeature feature, final DefaultAttributeType<V> type) {
         if (isSingleton(type.getMaximumOccurs())) {
             return new Singleton<>(feature, type);
         } else {
@@ -83,27 +88,31 @@
         return type.getName();
     }
 
-    /**
-     * Returns the type specified at construction time.
-     */
     @Override
-    public final AttributeType<V> getType() {
-        return type;
+    public V getValue() {
+        return type.getValueClass().cast(PropertyView.getValue(feature, name));
     }
 
-    /**
-     * Returns the class of values.
-     */
     @Override
-    final Class<V> getValueClass() {
-        return type.getValueClass();
+    public void setValue(final V value) {
+        PropertyView.setValue(feature, name, value);
+    }
+
+    @Override
+    public Collection<V> getValues() {
+        return PropertyView.getValues(feature, name, type.getValueClass());
+    }
+
+    @Override
+    public final void setValues(final Collection<? extends V> values) {
+        PropertyView.setValues(feature, name, values);
     }
 
     /**
      * Returns an empty map since this simple view does not support characteristics.
      */
     @Override
-    public final Map<String,Attribute<?>> characteristics() {
+    public final Map<String,AbstractAttribute<?>> characteristics() {
         return Collections.emptyMap();
     }
 
@@ -121,7 +130,7 @@
         /**
          * Creates a new attribute which will delegate its work to the given feature.
          */
-        Singleton(final Feature feature, final AttributeType<V> type) {
+        Singleton(final AbstractFeature feature, final DefaultAttributeType<V> type) {
             super(feature, type);
         }
 
@@ -135,7 +144,7 @@
 
         /**
          * Sets the value of this attribute. This method assumes that the
-         * {@link Feature#setPropertyValue(String, Object)} implementation
+         * {@code Feature.setPropertyValue(String, Object)} implementation
          * will verify the argument type.
          */
         @Override
@@ -148,7 +157,29 @@
          */
         @Override
         public Collection<V> getValues() {
-            return singletonOrEmpty(getValue());
+            return PropertyView.singletonOrEmpty(getValue());
         }
     }
+
+    @Override
+    public final int hashCode() {
+        return PropertyView.hashCode(feature, name);
+    }
+
+    @Override
+    public final boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj != null && obj.getClass() == getClass()) {
+            final AttributeView<?> that = (AttributeView<?>) obj;
+            return feature == that.feature && Objects.equals(name, that.name);
+        }
+        return false;
+    }
+
+    @Override
+    public final String toString() {
+        return PropertyView.toString(getClass(), type.getValueClass(), getName(), getValues());
+    }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java b/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
index 21539d5..6cdf076 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
@@ -24,12 +24,6 @@
 import org.apache.sis.internal.util.AbstractMapEntry;
 import org.apache.sis.internal.feature.Resources;
 
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * Implementation of {@link AbstractAttribute#characteristics()} map.
@@ -40,16 +34,16 @@
  * @since   0.5
  * @module
  */
-final class CharacteristicMap extends AbstractMap<String,Attribute<?>> implements Cloneable {
+final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> implements Cloneable {
     /**
      * The attribute source for which to provide characteristics.
      */
-    private final Attribute<?> source;
+    private final AbstractAttribute<?> source;
 
     /**
      * Characteristics of the {@code source} attribute, created when first needed.
      */
-    Attribute<?>[] characterizedBy;
+    AbstractAttribute<?>[] characterizedBy;
 
     /**
      * Description of the attribute characteristics.
@@ -62,7 +56,7 @@
      * @param  source  the attribute which is characterized by {@code characterizedBy}.
      * @param  types   description of the characteristics of {@code source}.
      */
-    CharacteristicMap(final Attribute<?> source, final CharacteristicTypeMap types) {
+    CharacteristicMap(final AbstractAttribute<?> source, final CharacteristicTypeMap types) {
         this.source = source;
         this.types  = types;
     }
@@ -75,14 +69,14 @@
     @Override
     public CharacteristicMap clone() throws CloneNotSupportedException {
         final CharacteristicMap clone = (CharacteristicMap) super.clone();
-        Attribute<?>[] c = clone.characterizedBy;
+        AbstractAttribute<?>[] c = clone.characterizedBy;
         if (c != null) {
             clone.characterizedBy = c = c.clone();
             final Cloner cloner = new Cloner();
             for (int i=0; i<c.length; i++) {
-                final Attribute<?> attribute = c[i];
+                final AbstractAttribute<?> attribute = c[i];
                 if (attribute instanceof Cloneable) {
-                    c[i] = (Attribute<?>) cloner.clone(attribute);
+                    c[i] = (AbstractAttribute<?>) cloner.clone(attribute);
                 }
             }
         }
@@ -108,7 +102,7 @@
     @Override
     public boolean isEmpty() {
         if (characterizedBy != null) {
-            for (final Attribute<?> attribute : characterizedBy) {
+            for (final AbstractAttribute<?> attribute : characterizedBy) {
                 if (attribute != null) {
                     return false;
                 }
@@ -124,7 +118,7 @@
     public int size() {
         int n = 0;
         if (characterizedBy != null) {
-            for (final Attribute<?> attribute : characterizedBy) {
+            for (final AbstractAttribute<?> attribute : characterizedBy) {
                 if (attribute != null) {
                     n++;
                 }
@@ -137,7 +131,7 @@
      * Returns the attribute characteristic for the given name, or {@code null} if none.
      */
     @Override
-    public Attribute<?> get(final Object key) {
+    public AbstractAttribute<?> get(final Object key) {
         if (characterizedBy != null) {
             final Integer index = types.indices.get(key);
             if (index != null) {
@@ -151,11 +145,11 @@
      * Removes the attribute characteristic for the given name.
      */
     @Override
-    public Attribute<?> remove(final Object key) {
+    public AbstractAttribute<?> remove(final Object key) {
         if (characterizedBy != null) {
             final Integer index = types.indices.get(key);
             if (index != null) {
-                final Attribute<?> previous = characterizedBy[index];
+                final AbstractAttribute<?> previous = characterizedBy[index];
                 characterizedBy[index] = null;
                 return previous;
             }
@@ -168,13 +162,13 @@
      *
      * @param  key  the name for which to get the characteristic index.
      * @return the index for the characteristic of the given name.
-     * @throws PropertyNotFoundException if the given key is not the name of a characteristic in this map.
+     * @throws IllegalArgumentException if the given key is not the name of a characteristic in this map.
      */
     private int indexOf(final String key) {
         ArgumentChecks.ensureNonNull("key", key);
         final Integer index = types.indices.get(key);
         if (index == null) {
-            throw new PropertyNotFoundException(Resources.format(
+            throw new IllegalArgumentException(Resources.format(
                     Resources.Keys.CharacteristicsNotFound_2, source.getName(), key));
         }
         return index;
@@ -188,12 +182,12 @@
      * @param  index  index of the expected attribute type.
      * @param  type   the actual attribute type.
      */
-    final void verifyAttributeType(final int index, final AttributeType<?> type) {
-        final AttributeType<?> expected = types.characterizedBy[index];
+    final void verifyAttributeType(final int index, final DefaultAttributeType<?> type) {
+        final DefaultAttributeType<?> expected = types.characterizedBy[index];
         if (!expected.equals(type)) {
             final GenericName en = expected.getName();
             final GenericName an = type.getName();
-            throw new InvalidPropertyValueException(String.valueOf(en).equals(String.valueOf(an))
+            throw new IllegalArgumentException(String.valueOf(en).equals(String.valueOf(an))
                     ? Resources.format(Resources.Keys.MismatchedPropertyType_1, en)
                     : Resources.format(Resources.Keys.CanNotSetCharacteristics_2, en.push(source.getName()), an));
         }
@@ -206,14 +200,14 @@
      * @throws IllegalArgumentException if the given key is not the name of a characteristic in this map.
      */
     @Override
-    public Attribute<?> put(final String key, final Attribute<?> value) {
+    public AbstractAttribute<?> put(final String key, final AbstractAttribute<?> value) {
         final int index = indexOf(key);
         ArgumentChecks.ensureNonNull("value", value);
         verifyAttributeType(index, value.getType());
         if (characterizedBy == null) {
-            characterizedBy = new Attribute<?>[types.characterizedBy.length];
+            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
         }
-        final Attribute<?> previous = characterizedBy[index];
+        final AbstractAttribute<?> previous = characterizedBy[index];
         characterizedBy[index] = value;
         return previous;
     }
@@ -230,7 +224,7 @@
     protected boolean addKey(final String name) {
         final int index = indexOf(name);
         if (characterizedBy == null) {
-            characterizedBy = new Attribute<?>[types.characterizedBy.length];
+            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
         }
         if (characterizedBy[index] == null) {
             characterizedBy[index] = types.characterizedBy[index].newInstance();
@@ -248,14 +242,14 @@
      * @throws IllegalStateException if another characteristic already exists for the characteristic name.
      */
     @Override
-    protected boolean addValue(final Attribute<?> value) {
+    protected boolean addValue(final AbstractAttribute<?> value) {
         ArgumentChecks.ensureNonNull("value", value);
         final int index = indexOf(value.getName().toString());
         verifyAttributeType(index, value.getType());
         if (characterizedBy == null) {
-            characterizedBy = new Attribute<?>[types.characterizedBy.length];
+            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
         }
-        final Attribute<?> previous = characterizedBy[index];
+        final AbstractAttribute<?> previous = characterizedBy[index];
         if (previous == null) {
             characterizedBy[index] = value;
             return true;
@@ -271,16 +265,16 @@
      * Returns an iterator over the entries.
      */
     @Override
-    protected EntryIterator<String, Attribute<?>> entryIterator() {
+    protected EntryIterator<String, AbstractAttribute<?>> entryIterator() {
         if (characterizedBy == null) {
             return null;
         }
-        return new EntryIterator<String, Attribute<?>>() {
+        return new EntryIterator<String, AbstractAttribute<?>>() {
             /** Index of the current element to return in the iteration. */
             private int index = -1;
 
             /** The element to return, or {@code null} if we reached the end of iteration. */
-            private Attribute<?> value;
+            private AbstractAttribute<?> value;
 
             /** Returns {@code true} if there is more entries in the iteration. */
             @Override protected boolean next() {
@@ -298,12 +292,12 @@
             }
 
             /** Returns the attribute characteristic (never {@code null}). */
-            @Override protected Attribute<?> getValue() {
+            @Override protected AbstractAttribute<?> getValue() {
                 return value;
             }
 
             /** Creates and return the next entry. */
-            @Override protected Map.Entry<String, Attribute<?>> getEntry() {
+            @Override protected Map.Entry<String, AbstractAttribute<?>> getEntry() {
                 return new Entry(index, value);
             }
 
@@ -317,17 +311,17 @@
     /**
      * An entry returned by the {@link CharacteristicMap#entrySet()} iterator.
      * The key and value are never null, even in case of concurrent modification.
-     * This entry supports the {@link #setValue(Attribute)} operation.
+     * This entry supports the {@code setValue(Attribute)} operation.
      */
-    private final class Entry extends AbstractMapEntry<String, Attribute<?>> {
+    private final class Entry extends AbstractMapEntry<String, AbstractAttribute<?>> {
         /** Index of the attribute characteristics represented by this entry. */
         private final int index;
 
         /** The current attribute value, which is guaranteed to be non-null. */
-        private Attribute<?> value;
+        private AbstractAttribute<?> value;
 
         /** Creates a new entry for the characteristic at the given index. */
-        Entry(final int index, final Attribute<?> value) {
+        Entry(final int index, final AbstractAttribute<?> value) {
             this.index = index;
             this.value = value;
         }
@@ -338,15 +332,15 @@
         }
 
         /** Returns the attribute characteristic (never {@code null}). */
-        @Override public Attribute<?> getValue() {
+        @Override public AbstractAttribute<?> getValue() {
             return value;
         }
 
         /** Sets the attribute characteristic. */
-        @Override public Attribute<?> setValue(final Attribute<?> value) {
+        @Override public AbstractAttribute<?> setValue(final AbstractAttribute<?> value) {
             ArgumentChecks.ensureNonNull("value", value);
             verifyAttributeType(index, value.getType());
-            final Attribute<?> previous = this.value;
+            final AbstractAttribute<?> previous = this.value;
             characterizedBy[index] = value;
             this.value = value;
             return previous;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java b/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java
index 0c11088..ae2080a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java
@@ -28,9 +28,6 @@
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
 
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Implementation of the map returned by {@link DefaultAttributeType#characteristics()}.
@@ -40,7 +37,7 @@
  * The straightforward approach would be to store the attributes directly as values in a standard {@code HashMap}.
  * But instead of that, we store attributes in an array and the array indices in a {@code HashMap}. This level of
  * indirection is useless if we consider only the {@link DefaultAttributeType#characteristics()} method, since a
- * standard {@code HashMap<String,AttributeType>} would work as well or better. However this level of indirection
+ * standard {@code HashMap<String,DefaultAttributeType>} would work as well or better. However this level of indirection
  * become useful for {@link CharacteristicMap} (the map returned by {@link AbstractAttribute#characteristics()}),
  * since it allows a more efficient storage. We do this effort because some applications may create a very large
  * amount of attribute instances.
@@ -50,17 +47,17 @@
  * @since   0.5
  * @module
  */
-final class CharacteristicTypeMap extends AbstractMap<String,AttributeType<?>> {
+final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeType<?>> {
     /**
      * For sharing the same {@code CharacteristicTypeMap} instances among the attribute types
      * having the same characteristics.
      */
     @SuppressWarnings("unchecked")
-    private static final WeakValueHashMap<AttributeType<?>[],CharacteristicTypeMap> SHARED =
-            new WeakValueHashMap<>((Class) AttributeType[].class);
+    private static final WeakValueHashMap<DefaultAttributeType<?>[],CharacteristicTypeMap> SHARED =
+            new WeakValueHashMap<>((Class) DefaultAttributeType[].class);
 
     /*
-     * This class has intentionally no reference to the AttributeType for which we are providing characteristics.
+     * This class has intentionally no reference to the DefaultAttributeType for which we are providing characteristics.
      * This allows us to use the same CharacteristicTypeMap instance for various attribute types having the same
      * characteristic (e.g. many measurements may have an "accuracy" characteristic).
      */
@@ -69,7 +66,7 @@
      * Characteristics of an other attribute type (the {@code source} attribute given to the constructor).
      * This array shall not be modified.
      */
-    final AttributeType<?>[] characterizedBy;
+    final DefaultAttributeType<?>[] characterizedBy;
 
     /**
      * The names of attribute types listed in the {@link #characterizedBy} array,
@@ -88,7 +85,7 @@
      * @return a map for this given characteristics.
      * @throws IllegalArgumentException if two characteristics have the same name.
      */
-    static CharacteristicTypeMap create(final AttributeType<?> source, final AttributeType<?>[] characterizedBy) {
+    static CharacteristicTypeMap create(final DefaultAttributeType<?> source, final DefaultAttributeType<?>[] characterizedBy) {
         CharacteristicTypeMap map;
         synchronized (SHARED) {
             map = SHARED.get(characterizedBy);
@@ -110,13 +107,13 @@
      * @param  characterizedBy  characteristics of {@code source}. Should not be empty.
      * @throws IllegalArgumentException if two characteristics have the same name.
      */
-    private CharacteristicTypeMap(final AttributeType<?> source, final AttributeType<?>[] characterizedBy) {
+    private CharacteristicTypeMap(final DefaultAttributeType<?> source, final DefaultAttributeType<?>[] characterizedBy) {
         this.characterizedBy = characterizedBy;
         int index = 0;
         final Map<String,Integer> indices = new HashMap<>(Containers.hashMapCapacity(characterizedBy.length));
         final Map<String,Integer> aliases = new HashMap<>();
         for (int i=0; i<characterizedBy.length; i++) {
-            final AttributeType<?> attribute = characterizedBy[i];
+            final DefaultAttributeType<?> attribute = characterizedBy[i];
             ensureNonNullElement("characterizedBy", i, attribute);
             GenericName name = attribute.getName();
             String key = AbstractIdentifiedType.toString(name, source, "characterizedBy", i);
@@ -181,7 +178,7 @@
      */
     @Override
     public boolean containsValue(final Object key) {
-        for (final AttributeType<?> type : characterizedBy) {
+        for (final DefaultAttributeType<?> type : characterizedBy) {
             if (type.equals(key)) {
                 return true;
             }
@@ -193,7 +190,7 @@
      * Returns the attribute characteristic for the given name, or {@code null} if none.
      */
     @Override
-    public AttributeType<?> get(final Object key) {
+    public DefaultAttributeType<?> get(final Object key) {
         final Integer index = indices.get(key);
         return (index != null) ? characterizedBy[index] : null;
     }
@@ -203,13 +200,13 @@
      * This is not the iterator returned by public API like {@code Map.entrySet().iterator()}.
      */
     @Override
-    protected EntryIterator<String, AttributeType<?>> entryIterator() {
-        return new EntryIterator<String, AttributeType<?>>() {
+    protected EntryIterator<String, DefaultAttributeType<?>> entryIterator() {
+        return new EntryIterator<String, DefaultAttributeType<?>>() {
             /** Index of the next element to return in the iteration. */
             private int index;
 
             /** Value of current entry. */
-            private AttributeType<?> value;
+            private DefaultAttributeType<?> value;
 
             /**
              * Returns {@code true} if there is more entries in the iteration.
@@ -235,7 +232,7 @@
              * Returns the attribute characteristic contained in this entry.
              */
             @Override
-            protected AttributeType<?> getValue() {
+            protected DefaultAttributeType<?> getValue() {
                 return value;
             }
         };
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/CommonParentFinder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/CommonParentFinder.java
index 18c30b0..6e224e9 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/CommonParentFinder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/CommonParentFinder.java
@@ -20,13 +20,10 @@
 import java.util.LinkedHashMap;
 import java.util.function.Predicate;
 
-// Branch-dependent imports
-import org.opengis.feature.FeatureType;
-
 
 /**
  * Finds a feature type common to all given types. This is either one of the given types, or a parent common to all types.
- * A feature <var>F</var> is considered a common parent if <code>F.{@link DefaultFeatureType#isAssignableFrom(FeatureType)
+ * A feature <var>F</var> is considered a common parent if <code>F.{@link DefaultFeatureType#isAssignableFrom(DefaultFeatureType)
  * isAssignableFrom}(type)</code> returns {@code true} for all elements <var>type</var> in the given array.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -41,16 +38,16 @@
      * @param  types  types for which to find a common type.
      * @return a feature type which is assignable from all given types, or {@code null} if none.
      */
-    static FeatureType select(final Iterable<? extends FeatureType> types) {
+    static DefaultFeatureType select(final Iterable<? extends DefaultFeatureType> types) {
         /*
          * Get a set of unique feature types. Since the process done in this method may be relatively costly,
          * we want to avoid doing the same work twice. The purpose of Boolean value will be explained later.
          */
-        final Map<FeatureType,Boolean> allTypes = new LinkedHashMap<>();
+        final Map<DefaultFeatureType,Boolean> allTypes = new LinkedHashMap<>();
         types.forEach((type) -> allTypes.putIfAbsent(type, Boolean.FALSE));
         allTypes.remove(null);
         int count = allTypes.size();
-        final FeatureType[] required = allTypes.keySet().toArray(new FeatureType[count]);
+        final DefaultFeatureType[] required = allTypes.keySet().toArray(new DefaultFeatureType[count]);
         /*
          * We are going to iterate over the `required` array many times. For performance reason, this array should be as
          * short as possible. We can make it shorter by removing any element A which is assignable to another element B,
@@ -58,7 +55,7 @@
          * If this simplification results in an array of only one element, we are done.
          */
         for (int i=0; i<count; i++) {
-            final FeatureType parent = required[i];
+            final DefaultFeatureType parent = required[i];
             for (int j=count; --j >= 0;) {                 // Reverse order so that removed elements do not impact index j.
                 if (j != i && parent.isAssignableFrom(required[j])) {
                     System.arraycopy(required, j+1, required, j, --count - j);      // If A is assignable from B, remove B.
@@ -84,13 +81,13 @@
      * updated after we find that a type {@linkplain #isAssignableFromAll is assignable from
      * all required types}.
      */
-    private final Map<FeatureType,Boolean> allTypes;
+    private final Map<DefaultFeatureType,Boolean> allTypes;
 
     /**
      * The features types which must be assignable to the common parent.
      * This array may not contain all feature types given by user, since we try to remove redundant elements.
      */
-    private final FeatureType[] required;
+    private final DefaultFeatureType[] required;
 
     /**
      * Number of valid elements in the {@link #required} array.
@@ -101,7 +98,7 @@
      * Creates a finder for a common parent of the given types.
      * Invokes {@link #select()} after construction time for getting the parent.
      */
-    private CommonParentFinder(final Map<FeatureType,Boolean> allTypes, final FeatureType[] required, final int count) {
+    private CommonParentFinder(final Map<DefaultFeatureType,Boolean> allTypes, final DefaultFeatureType[] required, final int count) {
         this.allTypes = allTypes;
         this.required = required;
         this.count    = count;
@@ -114,9 +111,9 @@
      * Returns {@code true} if the given parent candidate is assignable from all required types.
      * The feature type to be returned by {@link #select()} must met that condition.
      */
-    private boolean isAssignableFromAll(final FeatureType parent) {
+    private boolean isAssignableFromAll(final DefaultFeatureType parent) {
         for (int i=0; i<count; i++) {
-            final FeatureType type = required[i];
+            final DefaultFeatureType type = required[i];
             if (type != parent && !parent.isAssignableFrom(type)) {
                 return false;
             }
@@ -129,8 +126,8 @@
      * This method verifies recursively parents of parents, skipping types that have already
      * been examined in a previous invocation of this method.
      */
-    private void scanParents(final FeatureType type) {
-        for (final FeatureType parent : type.getSuperTypes()) {
+    private void scanParents(final DefaultFeatureType type) {
+        for (final DefaultFeatureType parent : type.getSuperTypes()) {
             if (allTypes.putIfAbsent(parent, Boolean.FALSE) == null) {
                 if (isAssignableFromAll(parent)) {
                     allTypes.put(parent, Boolean.TRUE);     // Found a candidate.
@@ -146,9 +143,9 @@
      * Invoked when the given feature type is assignable from all required types.
      * There is no need to verify the parents since they are not going to be a better match.
      */
-    private void skipParents(final FeatureType type) {
+    private void skipParents(final DefaultFeatureType type) {
         assert isAssignableFromAll(type);
-        for (final FeatureType parent : type.getSuperTypes()) {
+        for (final DefaultFeatureType parent : type.getSuperTypes()) {
             if (Boolean.TRUE.equals(allTypes.put(parent, Boolean.FALSE))) {
                 // If `parent` was previously a candidate, its parents have already been set to `FALSE`.
             } else {
@@ -161,11 +158,11 @@
      * Invoked after all feature types have been examined. This method removes all features types that
      * are not parent of required types, then select the one having the greatest number of properties.
      */
-    FeatureType select() {
+    DefaultFeatureType select() {
         allTypes.values().removeIf(Predicate.isEqual(Boolean.FALSE));
-        FeatureType best = null;
+        DefaultFeatureType best = null;
         int numProperties = 0;
-        for (final FeatureType type : allTypes.keySet()) {
+        for (final DefaultFeatureType type : allTypes.keySet()) {
             final int n = type.getProperties(true).size();
             if (best == null || n > numProperties) {
                 best = type;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
index ef079ac..4fdc3e3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
@@ -29,14 +29,6 @@
 
 import static org.apache.sis.util.ArgumentChecks.*;
 
-// Branch-dependent imports
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * Indicates the role played by the association between two features.
@@ -64,7 +56,7 @@
  * @since 0.5
  * @module
  */
-public class DefaultAssociationRole extends FieldType implements FeatureAssociationRole {
+public class DefaultAssociationRole extends FieldType {
     /**
      * For cross-version compatibility.
      */
@@ -81,8 +73,6 @@
      * The name of the property to use as a title for the associated feature, or an empty string if none.
      * This field is initially null, then computed when first needed.
      * This information is used only by {@link AbstractAssociation#toString()} implementation.
-     *
-     * @see #getTitleProperty(FeatureAssociationRole)
      */
     private transient volatile String titleProperty;
 
@@ -133,7 +123,7 @@
      *
      * @see org.apache.sis.feature.builder.AssociationRoleBuilder
      */
-    public DefaultAssociationRole(final Map<String,?> identification, final FeatureType valueType,
+    public DefaultAssociationRole(final Map<String,?> identification, final DefaultFeatureType valueType,
             final int minimumOccurs, final int maximumOccurs)
     {
         super(identification, minimumOccurs, maximumOccurs);
@@ -224,7 +214,7 @@
      *         invoking that method on {@code creating} may cause a failure with user code.
      * @return {@code true} if this association references a resolved feature type after this method call.
      */
-    final boolean resolve(final DefaultFeatureType creating, final Collection<PropertyType> properties) {
+    final boolean resolve(final DefaultFeatureType creating, final Collection<AbstractIdentifiedType> properties) {
         final FeatureType type = valueType;
         if (type instanceof NamedFeatureType) {
             FeatureType resolved = ((NamedFeatureType) type).resolved;
@@ -238,7 +228,7 @@
                      * this desired feature in an association of the 'creating' feature, instead of beeing
                      * the 'creating' feature itself. This is a little bit unusual, but not illegal.
                      */
-                    final List<FeatureType> deferred = new ArrayList<>();
+                    final List<DefaultFeatureType> deferred = new ArrayList<>();
                     resolved = search(creating, properties, name, deferred);
                     if (resolved == null) {
                         /*
@@ -273,8 +263,8 @@
      * @return the feature of the given name, or {@code null} if none.
      */
     @SuppressWarnings("null")
-    private static FeatureType search(final FeatureType feature, Collection<? extends PropertyType> properties,
-            final GenericName name, final List<FeatureType> deferred)
+    private static DefaultFeatureType search(final DefaultFeatureType feature, Collection<? extends AbstractIdentifiedType> properties,
+            final GenericName name, final List<DefaultFeatureType> deferred)
     {
         /*
          * Search only in associations declared in the given feature, not in inherited associations.
@@ -284,21 +274,17 @@
         if (properties == null) {
             properties = feature.getProperties(false);
         }
-        for (final PropertyType property : properties) {
-            if (property instanceof FeatureAssociationRole) {
+        for (final AbstractIdentifiedType property : properties) {
+            if (property instanceof DefaultAssociationRole) {
                 final FeatureType valueType;
-                if (property instanceof DefaultAssociationRole) {
-                    valueType = ((DefaultAssociationRole) property).valueType;
-                    if (valueType instanceof NamedFeatureType) {
-                        continue;                                   // Skip unresolved feature types.
-                    }
-                } else {
-                    valueType = ((FeatureAssociationRole) property).getValueType();
+                valueType = ((DefaultAssociationRole) property).valueType;
+                if (valueType instanceof NamedFeatureType) {
+                    continue;                                       // Skip unresolved feature types.
                 }
                 if (name.equals(valueType.getName())) {
-                    return valueType;
+                    return (DefaultFeatureType) valueType;
                 }
-                deferred.add(valueType);
+                deferred.add((DefaultFeatureType) valueType);
             }
         }
         /*
@@ -307,7 +293,7 @@
          * but not necessarily the same feature type (may be a subtype). This is equivalent to
          * "covariant return type" in the Java language.
          */
-        for (FeatureType type : feature.getSuperTypes()) {
+        for (DefaultFeatureType type : feature.getSuperTypes()) {
             if (name.equals(type.getName())) {
                 return type;
             }
@@ -320,7 +306,7 @@
     }
 
     /**
-     * Potentially invoked after {@link #search(FeatureType, Collection, GenericName, List)} for searching
+     * Potentially invoked after {@code search(FeatureType, Collection, GenericName, List)} for searching
      * in associations of associations.
      *
      * <p>Current implementation does not check that there are no duplicated names. Even if we did so,
@@ -328,14 +314,14 @@
      * later. We rather put a warning in {@link #DefaultAssociationRole(Map, GenericName, int, int)}
      * javadoc.</p>
      *
-     * @param  deferred  the feature types collected by {@link #search(FeatureType, Collection, GenericName, List)}.
+     * @param  deferred  the feature types collected by {@code search(FeatureType, Collection, GenericName, List)}.
      * @param  name      the name of the feature to search.
      * @return the feature of the given name, or {@code null} if none.
      */
-    private static FeatureType deepSearch(final List<FeatureType> deferred, final GenericName name) {
+    private static DefaultFeatureType deepSearch(final List<DefaultFeatureType> deferred, final GenericName name) {
         final Map<FeatureType,Boolean> done = new IdentityHashMap<>(8);
         for (int i=0; i<deferred.size();) {
-            FeatureType valueType = deferred.get(i++);
+            DefaultFeatureType valueType = deferred.get(i++);
             if (done.put(valueType, Boolean.TRUE) == null) {
                 deferred.subList(0, i).clear();                 // Discard previous value for making more room.
                 valueType = search(valueType, null, name, deferred);
@@ -352,19 +338,20 @@
      * Returns the type of feature values.
      *
      * <p>This method can not be invoked if {@link #isResolved()} returns {@code false}.
-     * However it is still possible to {@linkplain Features#getValueTypeName(PropertyType)
+     * However it is still possible to {@linkplain Features#getValueTypeName
      * get the associated feature type name}.</p>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.FeatureType}. This change is pending GeoAPI revision.</div>
+     *
      * @return the type of feature values.
      * @throws IllegalStateException if the feature type has been specified
      *         {@linkplain #DefaultAssociationRole(Map, GenericName, int, int) only by its name}
      *         and not yet resolved.
      *
      * @see #isResolved()
-     * @see Features#getValueTypeName(PropertyType)
      */
-    @Override
-    public final FeatureType getValueType() {
+    public final DefaultFeatureType getValueType() {
         /*
          * This method shall be final for consistency with other methods in this classes
          * which use the 'valueType' field directly. Furthermore, this method is invoked
@@ -378,15 +365,16 @@
             }
             valueType = type;
         }
-        return type;
+        return (DefaultFeatureType) type;
     }
 
     /**
      * Returns the name of the feature type. This information is always available
      * even when the name has not yet been {@linkplain #resolve resolved}.
      */
-    static GenericName getValueTypeName(final FeatureAssociationRole role) {
-        return (role instanceof DefaultAssociationRole ? ((DefaultAssociationRole) role).valueType : role.getValueType()).getName();
+    static GenericName getValueTypeName(final DefaultAssociationRole role) {
+        // Method is static for compatibility with branches on GeoAPI snapshots.
+        return role.valueType.getName();
     }
 
     /**
@@ -404,34 +392,35 @@
      *
      * This method should be used only for display purpose, not as a reliable or stable way to get the identifier.
      * The heuristic rules implemented in this method may change in any future Apache SIS version.
+     *
+     * <p><b>API note:</b> a non-static method would be more elegant in this "SIS for GeoAPI 3.0" branch.
+     * However this method needs to be static in other SIS branches, because they work with interfaces
+     * rather than SIS implementation. We keep the method static in this branch too for easier merges.</p>
      */
-    static String getTitleProperty(final FeatureAssociationRole role) {
-        if (role instanceof DefaultAssociationRole) {
-            String p = ((DefaultAssociationRole) role).titleProperty;       // No synchronization - not a big deal if computed twice.
-            if (p != null) {
-                return p.isEmpty() ? null : p;
-            }
-            p = searchTitleProperty(role.getValueType());
-            ((DefaultAssociationRole) role).titleProperty = (p != null) ? p : "";
-            return p;
+    static String getTitleProperty(final DefaultAssociationRole role) {
+        String p = role.titleProperty;       // No synchronization - not a big deal if computed twice.
+        if (p != null) {
+            return p.isEmpty() ? null : p;
         }
-        return searchTitleProperty(role.getValueType());
+        p = searchTitleProperty(role.getValueType());
+        role.titleProperty = (p != null) ? p : "";
+        return p;
     }
 
     /**
-     * Implementation of {@link #getTitleProperty(FeatureAssociationRole)} for first search,
+     * Implementation of {@link #getTitleProperty(DefaultAssociationRole)} for first search,
      * or for non-SIS {@code FeatureAssociationRole} implementations.
      */
-    private static String searchTitleProperty(final FeatureType ft) {
+    private static String searchTitleProperty(final DefaultFeatureType ft) {
         String fallback = null;
         try {
             return ft.getProperty(AttributeConvention.IDENTIFIER).getName().toString();
-        } catch (PropertyNotFoundException e) {
+        } catch (IllegalArgumentException e) {
             // Ignore.
         }
-        for (final PropertyType type : ft.getProperties(true)) {
-            if (type instanceof AttributeType<?>) {
-                final AttributeType<?> pt = (AttributeType<?>) type;
+        for (final AbstractIdentifiedType type : ft.getProperties(true)) {
+            if (type instanceof DefaultAttributeType<?>) {
+                final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) type;
                 final Class<?> valueClass = pt.getValueClass();
                 if (CharSequence.class.isAssignableFrom(valueClass) ||
                     GenericName .class.isAssignableFrom(valueClass) ||
@@ -478,10 +467,9 @@
      *
      * @return a new association instance.
      *
-     * @see AbstractAssociation#create(FeatureAssociationRole)
+     * @see AbstractAssociation#create(DefaultAssociationRole)
      */
-    @Override
-    public FeatureAssociation newInstance() {
+    public AbstractAssociation newInstance() {
         return AbstractAssociation.create(this);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
index 2c1fc7a..2401f8f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
@@ -30,10 +30,6 @@
 
 import static org.apache.sis.util.ArgumentChecks.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Definition of an attribute in a feature type.
@@ -47,6 +43,11 @@
  * Attribute characterization (discussed below) is similar to {@link java.lang.annotation.Annotation}.
  * </div>
  *
+ * <div class="warning"><b>Warning:</b>
+ * This class is expected to implement a GeoAPI {@code AttributeType} interface in a future version.
+ * When such interface will be available, most references to {@code DefaultAttributeType} in current
+ * API will be replaced by references to the {@code AttributeType} interface.</div>
+ *
  * <h2>Value type</h2>
  * Attributes can be used for both spatial and non-spatial properties.
  * Some examples are:
@@ -105,7 +106,7 @@
  * @since 0.5
  * @module
  */
-public class DefaultAttributeType<V> extends FieldType implements AttributeType<V> {
+public class DefaultAttributeType<V> extends FieldType {
     /**
      * For cross-version compatibility.
      */
@@ -188,7 +189,7 @@
     @SuppressWarnings("ThisEscapedInObjectConstruction")    // Okay because used only in package-private class.
     public DefaultAttributeType(final Map<String,?> identification, final Class<V> valueClass,
             final int minimumOccurs, final int maximumOccurs, final V defaultValue,
-            final AttributeType<?>... characterizedBy)
+            final DefaultAttributeType<?>... characterizedBy)
     {
         super(identification, minimumOccurs, maximumOccurs);
         ensureNonNull("valueClass",   valueClass);
@@ -221,7 +222,7 @@
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
         try {
-            final AttributeType<?>[] characterizedBy = (AttributeType<?>[]) in.readObject();
+            final DefaultAttributeType<?>[] characterizedBy = (DefaultAttributeType<?>[]) in.readObject();
             if (characterizedBy != null) {
                 characteristics = CharacteristicTypeMap.create(this, characterizedBy);
             }
@@ -235,10 +236,7 @@
      * Returns the type of attribute values.
      *
      * @return the type of attribute values.
-     *
-     * @see Features#getValueClass(PropertyType)
      */
-    @Override
     public final Class<V> getValueClass() {
         return valueClass;
     }
@@ -290,7 +288,6 @@
      *
      * @return the default value for the attribute, or {@code null} if none.
      */
-    @Override
     public V getDefaultValue() {
         return defaultValue;
     }
@@ -314,8 +311,7 @@
      *
      * @see AbstractAttribute#characteristics()
      */
-    @Override
-    public Map<String,AttributeType<?>> characteristics() {
+    public Map<String,DefaultAttributeType<?>> characteristics() {
         return (characteristics != null) ? characteristics : Collections.emptyMap();
     }
 
@@ -324,10 +320,9 @@
      *
      * @return a new attribute instance.
      *
-     * @see AbstractAttribute#create(AttributeType)
+     * @see AbstractAttribute#create(DefaultAttributeType)
      */
-    @Override
-    public Attribute<V> newInstance() {
+    public AbstractAttribute<V> newInstance() {
         return AbstractAttribute.create(this);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
index bd814c2..fa04848 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
@@ -40,17 +40,6 @@
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.internal.feature.Resources;
 
-// Branch-dependent imports
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.Operation;
-import org.opengis.feature.FeatureInstantiationException;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * Abstraction of a real-world phenomena. A {@code FeatureType} instance describes the class of all
@@ -60,6 +49,11 @@
  * compared to the Java language, {@code FeatureType} is equivalent to {@link Class} while
  * {@code Feature} instances are equivalent to {@link Object} instances of that class.</div>
  *
+ * <div class="warning"><b>Warning:</b>
+ * This class is expected to implement a GeoAPI {@code FeatureType} interface in a future version.
+ * When such interface will be available, most references to {@code DefaultFeatureType} in the API
+ * will be replaced by references to the {@code FeatureType} interface.</div>
+ *
  * <h2>Naming</h2>
  * The feature type {@linkplain #getName() name} is mandatory and should be unique. Those names are the main
  * criterion used for deciding if a feature type {@linkplain #isAssignableFrom is assignable from} another type.
@@ -92,7 +86,7 @@
  *
  * <h2>Immutability and thread safety</h2>
  * Instances of this class are immutable if all properties ({@link GenericName} and {@link InternationalString}
- * instances) and all arguments ({@link AttributeType} instances) given to the constructor are also immutable.
+ * instances) and all arguments ({@code AttributeType} instances) given to the constructor are also immutable.
  * Such immutable instances can be shared by many objects and passed between threads without synchronization.
  *
  * @author  Johann Sorel (Geomatys)
@@ -140,7 +134,7 @@
      * any unresolved name (i.e. a {@link DefaultAssociationRole#valueType} specified only be the
      * feature type name instead of its actual instance). A value of {@code true} means that all
      * names have been resolved. However a value of {@code false} only means that we are not sure,
-     * and that {@link #resolve(FeatureType, Map)} should check again.
+     * and that {@code resolve(FeatureType, Map)} should check again.
      *
      * <div class="note"><b>Note:</b>
      * Strictly speaking, this field should be declared {@code volatile} since the names could
@@ -157,13 +151,13 @@
      *
      * @see #getSuperTypes()
      */
-    private final Set<FeatureType> superTypes;
+    private final Set<DefaultFeatureType> superTypes;
 
     /**
-     * The names of all parents of this feature type, including parents of parents.
-     * This is used for a more efficient implementation of {@link #isAssignableFrom(FeatureType)}.
+     * The names of all parents of this feature type, including parents of parents. This is used
+     * for a more efficient implementation of {@link #isAssignableFrom(DefaultFeatureType)}.
      *
-     * @see #isAssignableFrom(FeatureType)
+     * @see #isAssignableFrom(DefaultFeatureType)
      */
     private transient Set<GenericName> assignableTo;
 
@@ -174,7 +168,7 @@
      *
      * @see #getProperties(boolean)
      */
-    private final List<PropertyType> properties;
+    private final List<AbstractIdentifiedType> properties;
 
     /**
      * All properties, including the ones declared in the super-types.
@@ -182,7 +176,7 @@
      *
      * @see #getProperties(boolean)
      */
-    private transient Collection<PropertyType> allProperties;
+    private transient Collection<AbstractIdentifiedType> allProperties;
 
     /**
      * A lookup table for fetching properties by name, including the properties from super-types.
@@ -190,7 +184,7 @@
      *
      * @see #getProperty(String)
      */
-    private transient Map<String, PropertyType> byName;
+    private transient Map<String, AbstractIdentifiedType> byName;
 
     /**
      * Indices of properties in an array of properties similar to {@link #properties},
@@ -247,6 +241,12 @@
      *   </tr>
      * </table>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the type of array elements may be
+     * changed to {@code org.opengis.feature.FeatureType} and {@code org.opengis.feature.PropertyType}.
+     * This change is pending GeoAPI revision. In the meantime, make sure that the {@code properties}
+     * array contains only attribute types, association roles or operations, <strong>not</strong> other
+     * feature types since the later are not properties in the ISO sense.</div>
+     *
      * @param identification  the name and other information to be given to this feature type.
      * @param isAbstract      if {@code true}, the feature type acts as an abstract super-type.
      * @param superTypes      the parents of this feature type, or {@code null} or empty if none.
@@ -257,7 +257,7 @@
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
     public DefaultFeatureType(final Map<String,?> identification, final boolean isAbstract,
-            final FeatureType[] superTypes, final PropertyType... properties)
+            final DefaultFeatureType[] superTypes, final AbstractIdentifiedType... properties)
     {
         super(identification);
         ArgumentChecks.ensureNonNull("properties", properties);
@@ -278,9 +278,9 @@
          * in case of duplicated values. Opportunistically verify for null values. The same verification could
          * be done in the scanPropertiesFrom(…) method, but doing it here produces a less confusing stacktrace.
          */
-        final List<PropertyType> sourceProperties = new ArrayList<>(properties.length);
+        final List<AbstractIdentifiedType> sourceProperties = new ArrayList<>(properties.length);
         for (int i=0; i<properties.length; i++) {
-            final PropertyType property = properties[i];
+            final AbstractIdentifiedType property = properties[i];
             ArgumentChecks.ensureNonNullElement("properties", i, property);
             sourceProperties.add(property);
         }
@@ -289,13 +289,13 @@
         switch (size) {
             case 0:  this.properties = Collections.emptyList(); break;
             case 1:  this.properties = Collections.singletonList(sourceProperties.get(0)); break;
-            default: this.properties = UnmodifiableArrayList.wrap(sourceProperties.toArray(new PropertyType[size])); break;
+            default: this.properties = UnmodifiableArrayList.wrap(sourceProperties.toArray(new AbstractIdentifiedType[size])); break;
         }
         /*
          * Before to resolve cyclic associations, verify that operations depend only on existing properties.
          * Note: the `allProperties` collection has been created by computeTransientFields(…) above.
          */
-        for (final PropertyType property : allProperties) {
+        for (final AbstractIdentifiedType property : allProperties) {
             if (property instanceof AbstractOperation) {
                 for (final String dependency : ((AbstractOperation) property).getDependencies()) {
                     if (!byName.containsKey(dependency)) {
@@ -343,17 +343,17 @@
      *
      * @param  properties  same content as {@link #properties} (may be the reference to the same list), but
      *         optionally in a temporarily modifiable list if we want to allow removal of duplicated values.
-     *         See {@link #scanPropertiesFrom(FeatureType, Collection)} javadoc for more explanation.
+     *         See {@code scanPropertiesFrom(FeatureType, Collection)} javadoc for more explanation.
      * @throws IllegalArgumentException if two properties have the same name.
      */
-    private void computeTransientFields(final List<PropertyType> properties) {
+    private void computeTransientFields(final List<AbstractIdentifiedType> properties) {
         final int capacity = Containers.hashMapCapacity(properties.size());
         byName       = new LinkedHashMap<>(capacity);
         indices      = new LinkedHashMap<>(capacity);
         assignableTo = new HashSet<>(4);
         assignableTo.add(super.getName());
         scanPropertiesFrom(this, properties);
-        allProperties = UnmodifiableArrayList.wrap(byName.values().toArray(new PropertyType[byName.size()]));
+        allProperties = UnmodifiableArrayList.wrap(byName.values().toArray(new AbstractIdentifiedType[byName.size()]));
         /*
          * Now check if the feature is simple/complex or dense/sparse. We perform this check after we finished
          * to create the list of all properties, because some properties may be overridden and we want to take
@@ -362,16 +362,16 @@
         isSimple = true;
         int index = 0;
         int mandatory = 0;                                                  // Count of mandatory properties.
-        for (final Map.Entry<String,PropertyType> entry : byName.entrySet()) {
+        for (final Map.Entry<String,AbstractIdentifiedType> entry : byName.entrySet()) {
             final int minimumOccurs, maximumOccurs;
-            final PropertyType property = entry.getValue();
-            if (property instanceof AttributeType<?>) {
-                minimumOccurs = ((AttributeType<?>) property).getMinimumOccurs();
-                maximumOccurs = ((AttributeType<?>) property).getMaximumOccurs();
+            final AbstractIdentifiedType property = entry.getValue();
+            if (property instanceof DefaultAttributeType<?>) { // Other SIS branches check for AttributeType instead.
+                minimumOccurs = ((DefaultAttributeType<?>) property).getMinimumOccurs();
+                maximumOccurs = ((DefaultAttributeType<?>) property).getMaximumOccurs();
                 isSimple &= (minimumOccurs == maximumOccurs);
-            } else if (property instanceof FeatureAssociationRole) {
-                minimumOccurs = ((FeatureAssociationRole) property).getMinimumOccurs();
-                maximumOccurs = ((FeatureAssociationRole) property).getMaximumOccurs();
+            } else if (property instanceof FieldType) { // TODO: check for AssociationRole instead (after GeoAPI upgrade).
+                minimumOccurs = ((FieldType) property).getMinimumOccurs();
+                maximumOccurs = ((FieldType) property).getMaximumOccurs();
                 isSimple = false;
             } else {
                 if (isParameterlessOperation(property)) {
@@ -394,8 +394,8 @@
          *
          * In the `aliases` map below, null values will be assigned to ambiguous short names.
          */
-        final Map<String, PropertyType> aliases = new LinkedHashMap<>();
-        for (final PropertyType property : allProperties) {
+        final Map<String, AbstractIdentifiedType> aliases = new LinkedHashMap<>();
+        for (final AbstractIdentifiedType property : allProperties) {
             GenericName name = property.getName();
             while (name instanceof ScopedName) {
                 if (name == (name = ((ScopedName) name).tail())) break;   // Safety against broken implementations.
@@ -404,8 +404,8 @@
                 aliases.put(key, aliases.containsKey(key) ? null : property);
             }
         }
-        for (final Map.Entry<String,PropertyType> entry : aliases.entrySet()) {
-            final PropertyType property = entry.getValue();
+        for (final Map.Entry<String,AbstractIdentifiedType> entry : aliases.entrySet()) {
+            final AbstractIdentifiedType property = entry.getValue();
             if (property != null) {
                 final String tip = entry.getKey();
                 if (byName.putIfAbsent(tip, property) == null) {
@@ -451,7 +451,7 @@
      * <ul>
      *   <li>Avoid a call to the user-overrideable {@link #getProperties(boolean)} method
      *       while this {@code DefaultFeatureType} instance is still under constructor.</li>
-     *   <li>Allow the {@link #DefaultFeatureType(Map, boolean, FeatureType[], PropertyType[])} constructor
+     *   <li>Allow the {@code DefaultFeatureType(Map, boolean, FeatureType[], PropertyType[])} constructor
      *       to pass a temporary modifiable list that allow element removal.</li>
      * </ul>
      *
@@ -459,18 +459,20 @@
      * @param  sourceProperties  {@code source.getProperties(false)} (see above method javadoc).
      * @throws IllegalArgumentException if two properties have the same name.
      */
-    private void scanPropertiesFrom(final FeatureType source, final Collection<? extends PropertyType> sourceProperties) {
-        for (final FeatureType parent : source.getSuperTypes()) {
+    private void scanPropertiesFrom(final DefaultFeatureType source,
+            final Collection<? extends AbstractIdentifiedType> sourceProperties)
+    {
+        for (final DefaultFeatureType parent : source.getSuperTypes()) {
             if (assignableTo.add(parent.getName())) {
                 scanPropertiesFrom(parent, parent.getProperties(false));
             }
         }
         int index = -1;
-        final Iterator<? extends PropertyType> it = sourceProperties.iterator();
+        final Iterator<? extends AbstractIdentifiedType> it = sourceProperties.iterator();
         while (it.hasNext()) {
-            final PropertyType property = it.next();
+            final AbstractIdentifiedType property = it.next();
             final String name = toString(property.getName(), source, "properties", ++index);
-            final PropertyType previous = byName.put(name, property);
+            final AbstractIdentifiedType previous = byName.put(name, property);
             if (previous != null) {
                 if (previous.equals(property)) {
                     byName.put(name, previous);         // Keep the instance declared in super-type.
@@ -490,14 +492,18 @@
      * Returns the name of the feature which defines the given property, or {@code null} if not found.
      * This method is for information purpose when producing an error message - its implementation does
      * not need to be efficient.
+     *
+     * <p><b>API note:</b> a non-static method would be more elegant in this "SIS for GeoAPI 3.0" branch.
+     * However this method needs to be static in other SIS branches, because they work with interfaces
+     * rather than SIS implementation. We keep the method static in this branch too for easier merges.</p>
      */
-    private static GenericName ownerOf(final FeatureType type, final Collection<? extends PropertyType> properties,
-            final PropertyType toSearch)
+    private static GenericName ownerOf(final DefaultFeatureType type, final Collection<? extends AbstractIdentifiedType> properties,
+            final AbstractIdentifiedType toSearch)
     {
         if (properties.contains(toSearch)) {
             return type.getName();
         }
-        for (final FeatureType superType : type.getSuperTypes()) {
+        for (final DefaultFeatureType superType : type.getSuperTypes()) {
             final GenericName owner = ownerOf(superType, superType.getProperties(false), toSearch);
             if (owner != null) {
                 return owner;
@@ -521,23 +527,18 @@
      * @param  previous  previous results, for avoiding never ending loop.
      * @return {@code true} if all names have been resolved.
      */
-    private boolean resolve(final FeatureType feature, final Map<FeatureType,Boolean> previous) {
+    private boolean resolve(final DefaultFeatureType feature, final Map<FeatureType,Boolean> previous) {
         /*
          * The isResolved field is used only as a cache for skipping completely the DefaultFeatureType instance if
          * we have determined that there is no unresolved name.  If the given argument is not a DefaultFeatureType
          * instance, conservatively assumes `isSimple`. It may cause more calculation than needed, but should not
          * change the result.
          */
-        if (feature instanceof DefaultFeatureType) {
-            final DefaultFeatureType dt = (DefaultFeatureType) feature;
-            return dt.isResolved = resolve(dt, dt.properties, previous, dt.isResolved);
-        } else {
-            return resolve(feature, feature.getProperties(false), previous, feature.isSimple());
-        }
+        return feature.isResolved = resolve(feature, feature.properties, previous, feature.isResolved);
     }
 
     /**
-     * Implementation of {@link #resolve(FeatureType, Map)}, also to be invoked from the constructor.
+     * Implementation of {@code resolve(FeatureType, Map)}, also to be invoked from the constructor.
      *
      * <p>{@code this} shall be the instance in process of being created, not other instance
      * (i.e. recursive method invocations are performed on the same {@code this} instance).</p>
@@ -548,21 +549,19 @@
      * @param  resolved    {@code true} if we already know that all names are resolved.
      * @return {@code true} if all names have been resolved.
      */
-    private boolean resolve(final FeatureType feature, final Collection<? extends PropertyType> toUpdate,
+    private boolean resolve(final DefaultFeatureType feature, final Collection<? extends AbstractIdentifiedType> toUpdate,
             Map<FeatureType,Boolean> previous, boolean resolved)
     {
         if (!resolved) {
             resolved = true;
-            for (final FeatureType type : feature.getSuperTypes()) {
+            for (final DefaultFeatureType type : feature.getSuperTypes()) {
                 resolved &= resolve(type, previous);
             }
-            for (final PropertyType property : toUpdate) {
-                if (property instanceof FeatureAssociationRole) {
-                    if (property instanceof DefaultAssociationRole) {
-                        if (!((DefaultAssociationRole) property).resolve(this, properties)) {
-                            resolved = false;
-                            continue;
-                        }
+            for (final AbstractIdentifiedType property : toUpdate) {
+                if (property instanceof DefaultAssociationRole) {
+                    if (!((DefaultAssociationRole) property).resolve(this, properties)) {
+                        resolved = false;
+                        continue;
                     }
                     /*
                      * Resolve recursively the associated features, with a check against infinite recursivity.
@@ -570,7 +569,7 @@
                      * may not be the most accurate answer, but will not cause any more hurt than checking more
                      * often than necessary.
                      */
-                    final FeatureType valueType = ((FeatureAssociationRole) property).getValueType();
+                    final DefaultFeatureType valueType = ((DefaultAssociationRole) property).getValueType();
                     if (valueType != this) {
                         if (previous == null) {
                             previous = new IdentityHashMap<>(8);
@@ -593,11 +592,11 @@
      *
      * @see #OPERATION_INDEX
      */
-    static boolean isParameterlessOperation(final PropertyType type) {
-        if (type instanceof Operation) {
-            final ParameterDescriptorGroup parameters = ((Operation) type).getParameters();
+    static boolean isParameterlessOperation(final AbstractIdentifiedType type) {
+        if (type instanceof AbstractOperation) {
+            final ParameterDescriptorGroup parameters = ((AbstractOperation) type).getParameters();
             return ((parameters == null) || parameters.descriptors().isEmpty())
-                   && ((Operation) type).getResult() != null;
+                   && ((AbstractOperation) type).getResult() != null;
         }
         return false;
     }
@@ -612,7 +611,6 @@
      *
      * @return {@code true} if the feature type acts as an abstract super-type.
      */
-    @Override
     public final boolean isAbstract() {
         return isAbstract;
     }
@@ -632,31 +630,22 @@
      *
      * @return {@code true} if this feature type contains only simple attributes or operations.
      */
-    @Override
     public boolean isSimple() {
         return isSimple;
     }
 
     /**
      * Returns {@code true} if the given base type may be the same or a super-type of the given type, using only
-     * the name as a criterion. This is a faster check than {@link #isAssignableFrom(FeatureType)}.
+     * the name as a criterion. This is a faster check than {@link #isAssignableFrom(DefaultFeatureType)}.
      *
      * <p>Performance note: callers should verify that {@code base != type} before to invoke this method.</p>
+     *
+     * <p><b>API note:</b> a non-static method would be more elegant in this "SIS for GeoAPI 3.0" branch.
+     * However this method needs to be static in other SIS branches, because they work with interfaces
+     * rather than SIS implementation. We keep the method static in this branch too for easier merges.</p>
      */
-    static boolean maybeAssignableFrom(final FeatureType base, final FeatureType type) {
-        if (type instanceof DefaultFeatureType) {
-            return ((DefaultFeatureType) type).assignableTo.contains(base.getName());
-        }
-        // Slower path for non-SIS implementations.
-        if (Objects.equals(base.getName(), type.getName())) {
-            return true;
-        }
-        for (final FeatureType superType : type.getSuperTypes()) {
-            if (base == superType || maybeAssignableFrom(base, superType)) {
-                return true;
-            }
-        }
-        return false;
+    static boolean maybeAssignableFrom(final DefaultFeatureType base, final DefaultFeatureType type) {
+        return type.assignableTo.contains(base.getName());
     }
 
     /**
@@ -679,7 +668,7 @@
      * @return {@code true} if instances of the given type can be assigned to association of this type.
      */
     @Override
-    public boolean isAssignableFrom(final FeatureType type) {
+    public boolean isAssignableFrom(final DefaultFeatureType type) {
         if (type == this) {
             return true;                            // Optimization for a common case.
         }
@@ -691,11 +680,11 @@
          * Ensures that all properties defined in this feature type is also defined
          * in the given property, and that the former is assignable from the latter.
          */
-        for (final Map.Entry<String, PropertyType> entry : byName.entrySet()) {
-            final PropertyType other;
+        for (final Map.Entry<String, AbstractIdentifiedType> entry : byName.entrySet()) {
+            final AbstractIdentifiedType other;
             try {
                 other = type.getProperty(entry.getKey());
-            } catch (PropertyNotFoundException e) {
+            } catch (IllegalArgumentException e) {
                 /*
                  * A property in this FeatureType does not exist in the given FeatureType.
                  * Catching exceptions is not an efficient way to perform this check, but
@@ -716,22 +705,25 @@
      * Returns {@code true} if instances of the {@code other} type are assignable to the given {@code base} type.
      * This method does not compare the names — this verification is presumed already done by the caller.
      */
-    private static boolean isAssignableIgnoreName(final PropertyType base, final PropertyType other) {
+    private static boolean isAssignableIgnoreName(final AbstractIdentifiedType base, final AbstractIdentifiedType other) {
         if (base != other) {
             /*
              * If the base property is an attribute, then the overriding property shall be either an attribute
              * or a parameterless operation producing an attribute.  The parameterless operation is considered
              * has having a [1…1] multiplicity.
+             *
+             * Note: other SIS branches use AttributeType and FeatureAssociationRole
+             *       instead than DefaultAttributeType and DefaultAssociationRole.
              */
-            if (base instanceof AttributeType<?>) {
-                final AttributeType<?> p0 = (AttributeType<?>) base;
-                final AttributeType<?> p1;
-                if (other instanceof AttributeType<?>) {
-                    p1 = (AttributeType<?>) other;
+            if (base instanceof DefaultAttributeType<?>) {
+                final DefaultAttributeType<?> p0 = (DefaultAttributeType<?>) base;
+                final DefaultAttributeType<?> p1;
+                if (other instanceof DefaultAttributeType<?>) {
+                    p1 = (DefaultAttributeType<?>) other;
                 } else if (isParameterlessOperation(other)) {
-                    final IdentifiedType result = ((Operation) other).getResult();
-                    if (result instanceof AttributeType<?>) {
-                        p1 = (AttributeType<?>) result;
+                    final AbstractIdentifiedType result = ((AbstractOperation) other).getResult();
+                    if (result instanceof DefaultAttributeType<?>) {
+                        p1 = (DefaultAttributeType<?>) result;
                     } else {
                         return false;
                     }
@@ -752,15 +744,15 @@
              * because an implementation could implement both AttributeType and AssociationRole interfaces.
              * This is not recommended, but if it happen we want a behavior as consistent as possible.
              */
-            if (base instanceof FeatureAssociationRole) {
-                final FeatureAssociationRole p0 = (FeatureAssociationRole) base;
-                final FeatureAssociationRole p1;
-                if (other instanceof FeatureAssociationRole) {
-                    p1 = (FeatureAssociationRole) other;
+            if (base instanceof DefaultAssociationRole) {
+                final DefaultAssociationRole p0 = (DefaultAssociationRole) base;
+                final DefaultAssociationRole p1;
+                if (other instanceof DefaultAssociationRole) {
+                    p1 = (DefaultAssociationRole) other;
                 } else if (isParameterlessOperation(other)) {
-                    final IdentifiedType result = ((Operation) other).getResult();
-                    if (result instanceof FeatureAssociationRole) {
-                        p1 = (FeatureAssociationRole) result;
+                    final AbstractIdentifiedType result = ((AbstractOperation) other).getResult();
+                    if (result instanceof DefaultAssociationRole) {
+                        p1 = (DefaultAssociationRole) result;
                     } else {
                         return false;
                     }
@@ -774,8 +766,8 @@
                 {
                     return false;
                 }
-                final FeatureType f0 = p0.getValueType();
-                final FeatureType f1 = p1.getValueType();
+                final DefaultFeatureType f0 = p0.getValueType();
+                final DefaultFeatureType f1 = p1.getValueType();
                 if (f0 != f1 && !f0.isAssignableFrom(f1)) {
                     return false;
                 }
@@ -785,11 +777,11 @@
              * In the special case of parameterless operations, can also be overridden by
              * AttributeType or FeatureAssociationRole.
              */
-            if (base instanceof Operation) {
-                final Operation p0 = (Operation) base;
-                final IdentifiedType r1;
-                if (other instanceof Operation) {
-                    final Operation p1 = (Operation) other;
+            if (base instanceof AbstractOperation) {
+                final AbstractOperation p0 = (AbstractOperation) base;
+                final AbstractIdentifiedType r1;
+                if (other instanceof AbstractOperation) {
+                    final AbstractOperation p1 = (AbstractOperation) other;
                     if (!Objects.equals(p0.getParameters(), p1.getParameters())) {
                         return false;
                     }
@@ -799,15 +791,14 @@
                 } else {
                     return false;
                 }
-                final IdentifiedType r0 = p0.getResult();
+                final AbstractIdentifiedType r0 = p0.getResult();
                 if (r0 != r1) {
-                    if (r0 instanceof FeatureType) {
-                        if (!(r1 instanceof FeatureType) || !((FeatureType) r0).isAssignableFrom((FeatureType) r1)) {
+                    if (r0 instanceof DefaultFeatureType) {
+                        if (!(r1 instanceof DefaultFeatureType) || !((FeatureType) r0).isAssignableFrom((DefaultFeatureType) r1)) {
                             return false;
                         }
-                    }
-                    if (r0 instanceof PropertyType) {
-                        if (!(r1 instanceof PropertyType) || !isAssignableIgnoreName((PropertyType) r0, (PropertyType) r1)) {
+                    } else if (r0 != null) {
+                        if (r1 == null || !isAssignableIgnoreName(r0, r1)) {
                             return false;
                         }
                     }
@@ -825,6 +816,10 @@
      * if we compare {@code FeatureType} to {@link Class} in the Java language, then this method is equivalent
      * to {@link Class#getSuperclass()} except that feature types allow multi-inheritance.</div>
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of list elements will be changed to {@code FeatureType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * <div class="note"><b>Note for subclasses:</b>
      * this method is final because it is invoked (indirectly) by constructors, and invoking a user-overrideable
      * method at construction time is not recommended. Furthermore, many Apache SIS methods need guarantees about
@@ -833,9 +828,8 @@
      *
      * @return  the parents of this feature type, or an empty set if none.
      */
-    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public final Set<FeatureType> getSuperTypes() {
+    public final Set<DefaultFeatureType> getSuperTypes() {
         return superTypes;      // Immutable
     }
 
@@ -845,32 +839,39 @@
      * inherited from the {@linkplain #getSuperTypes() super-types} only if {@code includeSuperTypes}
      * is {@code true}.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of list elements will be changed to {@code PropertyType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  includeSuperTypes  {@code true} for including the properties inherited from the super-types,
      *         or {@code false} for returning only the properties defined explicitly in this type.
      * @return feature operation, attribute type and association role that carries characteristics of this
      *         feature type (not including parent types).
      */
     @Override
-    public Collection<PropertyType> getProperties(final boolean includeSuperTypes) {
+    public Collection<AbstractIdentifiedType> getProperties(final boolean includeSuperTypes) {
         return includeSuperTypes ? allProperties : properties;
     }
 
     /**
      * Returns the attribute, operation or association role for the given name.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of returned element will be changed to {@code PropertyType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  name  the name of the property to search.
      * @return the property for the given name, or {@code null} if none.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      *
      * @see AbstractFeature#getProperty(String)
      */
-    @Override
-    public PropertyType getProperty(final String name) throws PropertyNotFoundException {
-        final PropertyType pt = byName.get(name);
+    public AbstractIdentifiedType getProperty(final String name) throws IllegalArgumentException {
+        final AbstractIdentifiedType pt = byName.get(name);
         if (pt != null) {
             return pt;
         }
-        throw new PropertyNotFoundException(AbstractFeature.propertyNotFound(this, getName(), name));
+        throw new IllegalArgumentException(AbstractFeature.propertyNotFound(this, getName(), name));
     }
 
     /**
@@ -890,12 +891,11 @@
      * then this method is equivalent to {@link Class#newInstance()}.</div>
      *
      * @return a new feature instance.
-     * @throws FeatureInstantiationException if this feature type {@linkplain #isAbstract() is abstract}.
+     * @throws IllegalStateException if this feature type {@linkplain #isAbstract() is abstract}.
      */
-    @Override
-    public Feature newInstance() throws FeatureInstantiationException {
+    public AbstractFeature newInstance() throws IllegalStateException {
         if (isAbstract) {
-            throw new FeatureInstantiationException(Resources.format(Resources.Keys.AbstractFeatureType_1, getName()));
+            throw new IllegalStateException(Resources.format(Resources.Keys.AbstractFeatureType_1, getName()));
         }
         return isSparse ? new SparseFeature(this) : new DenseFeature(this);
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
index 13564b3..43841fd 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
@@ -23,12 +23,6 @@
 import org.apache.sis.internal.util.Cloner;
 import org.apache.sis.util.ArgumentChecks;
 
-// Branch-dependent imports
-import org.opengis.feature.Property;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * A feature in which most properties are expected to be provided. This implementation uses a plain array for
@@ -85,14 +79,14 @@
      * @param  name  the property name.
      * @return the index for the property of the given name,
      *         or a negative value if the property is a parameterless operation.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
-    private int getIndex(final String name) throws PropertyNotFoundException {
+    private int getIndex(final String name) throws IllegalArgumentException {
         final Integer index = indices.get(name);
         if (index != null) {
             return index;
         }
-        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
+        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -100,10 +94,10 @@
      *
      * @param  name  the property name.
      * @return the property of the given name.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
     @Override
-    public Property getProperty(final String name) throws PropertyNotFoundException {
+    public Object getProperty(final String name) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("name", name);
         final int index = getIndex(name);
         if (index < 0) {
@@ -136,10 +130,10 @@
      *         known to this feature, or if the property can not be set or another reason.
      */
     @Override
-    public void setProperty(final Property property) throws IllegalArgumentException {
+    public void setProperty(final Object property) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("property", property);
-        final String name = property.getName().toString();
-        verifyPropertyType(name, property);
+        final String name = ((Property) property).getName().toString();
+        verifyPropertyType(name, (Property) property);
         if (!(properties instanceof Property[])) {
             wrapValuesInProperties();
         }
@@ -176,13 +170,13 @@
      *
      * @param  name  the property name.
      * @return the value for the given property, or {@code null} if none.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      */
     @Override
-    public Object getPropertyValue(final String name) throws PropertyNotFoundException {
+    public Object getPropertyValue(final String name) throws IllegalArgumentException {
         final Object value = getValueOrFallback(name, MISSING);
         if (value != MISSING) return value;
-        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
+        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -209,10 +203,10 @@
             if (element != null) {
                 if (!(properties instanceof Property[])) {
                     return element;                                         // Most common case.
-                } else if (element instanceof Attribute<?>) {
-                    return getAttributeValue((Attribute<?>) element);
-                } else if (element instanceof FeatureAssociation) {
-                    return getAssociationValue((FeatureAssociation) element);
+                } else if (element instanceof AbstractAttribute<?>) {
+                    return getAttributeValue((AbstractAttribute<?>) element);
+                } else if (element instanceof AbstractAssociation) {
+                    return getAssociationValue((AbstractAssociation) element);
                 } else {
                     throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
                 }
@@ -324,10 +318,10 @@
                 for (final Property p : (Property[]) properties) {
                     code = 31 * code;
                     final Object value;
-                    if (p instanceof Attribute<?>) {
-                        value = getAttributeValue((Attribute<?>) p);
-                    } else if (p instanceof FeatureAssociation) {
-                        value = getAssociationValue((FeatureAssociation) p);
+                    if (p instanceof AbstractAttribute<?>) {
+                        value = getAttributeValue((AbstractAttribute<?>) p);
+                    } else if (p instanceof AbstractAssociation) {
+                        value = getAssociationValue((AbstractAssociation) p);
                     } else {
                         continue;
                     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
index d5e5526..7cf2b8b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
@@ -41,14 +41,6 @@
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.util.resources.Errors;
 
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * An operation computing the envelope that encompass all geometries found in a list of attributes.
@@ -108,8 +100,8 @@
      * a default CRS.
      *
      * <h4>Performance note</h4>
-     * If this array is {@code null}, then {@link Feature#getProperty(String)} does not need to be invoked at all.
-     * A null array is a signal that invoking only the cheaper {@link Feature#getPropertyValue(String)} method is
+     * If this array is {@code null}, then {@link AbstractFeature#getProperty(String)} does not need to be invoked at all.
+     * A null array is a signal that invoking only the cheaper {@link AbstractFeature#getPropertyValue(String)} method is
      * sufficient. However this array become non-null as soon as there is at least one CRS characteristic to check.
      * We do not distinguish which particular property may have a CRS characteristic because as of Apache SIS 1.0,
      * implementations of {@link DenseFeature} and {@link SparseFeature} have a "all of nothing" behavior anyway.
@@ -125,7 +117,7 @@
     /**
      * The type of the result returned by the envelope operation.
      */
-    private final AttributeType<Envelope> resultType;
+    private final DefaultAttributeType<Envelope> resultType;
 
     /**
      * Creates a new operation computing the envelope of features of the given type.
@@ -135,7 +127,7 @@
      * @param geometryAttributes  the operation or attribute type from which to get geometry values.
      */
     EnvelopeOperation(final Map<String,?> identification, CoordinateReferenceSystem targetCRS,
-            final PropertyType[] geometryAttributes) throws FactoryException
+            final AbstractIdentifiedType[] geometryAttributes) throws FactoryException
     {
         super(identification);
         String defaultGeometry = null;
@@ -149,8 +141,8 @@
          */
         boolean characterizedByCRS = false;
         final Map<String,CoordinateReferenceSystem> names = new LinkedHashMap<>(4);
-        for (final IdentifiedType property : geometryAttributes) {
-            final Optional<AttributeType<?>> at = Features.toAttribute(property);
+        for (final AbstractIdentifiedType property : geometryAttributes) {
+            final Optional<DefaultAttributeType<?>> at = Features.toAttribute(property);
             if (at.isPresent() && Geometries.isKnownType(at.get().getValueClass())) {
                 final GenericName name = property.getName();
                 final String attributeName = (property instanceof LinkOperation)
@@ -165,7 +157,7 @@
                  * "CRS" characteristic. Note that we can not rely on `attributeCRS` being non-null
                  * because an attribute may be characterized by a CRS without providing default CRS.
                  */
-                final AttributeType<?> ct = at.get().characteristics().get(AttributeConvention.CRS);
+                final DefaultAttributeType<?> ct = at.get().characteristics().get(AttributeConvention.CRS);
                 if (ct != null && CoordinateReferenceSystem.class.isAssignableFrom(ct.getValueClass())) {
                     attributeCRS = (CoordinateReferenceSystem) ct.getDefaultValue();              // May still null.
                     if (targetCRS == null && isDefault) {
@@ -234,7 +226,7 @@
      * @return an {@code AttributeType<Envelope>}.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return resultType;
     }
 
@@ -259,7 +251,7 @@
      * @return the envelope of geometries in feature property values.
      */
     @Override
-    public Property apply(Feature feature, ParameterValueGroup parameters) {
+    public Property apply(AbstractFeature feature, ParameterValueGroup parameters) {
         return new Result(feature);
     }
 
@@ -277,14 +269,14 @@
         private static final long serialVersionUID = 926172863066901618L;
 
         /**
-         * The feature specified to the {@link StringJoinOperation#apply(Feature, ParameterValueGroup)} method.
+         * The feature specified to the {@code StringJoinOperation.apply(Feature, ParameterValueGroup)} method.
          */
-        private final Feature feature;
+        private final AbstractFeature feature;
 
         /**
          * Creates a new attribute for the given feature.
          */
-        Result(final Feature feature) {
+        Result(final AbstractFeature feature) {
             super(resultType);
             this.feature = feature;
         }
@@ -342,7 +334,7 @@
                          * a CRS characteristic is associated to a particular feature, setting `op` to null
                          * will cause a new coordinate operation to be searched.
                          */
-                        final Attribute<?> at = ((Attribute<?>) feature.getProperty(attributeNames[i]))
+                        final AbstractAttribute<?> at = ((AbstractAttribute<?>) feature.getProperty(attributeNames[i]))
                                 .characteristics().get(AttributeConvention.CRS);
                         final Object geomCRS;
                         if (at != null && (geomCRS = at.getValue()) != null) {
@@ -409,7 +401,7 @@
          */
         @Override
         public void setValue(Envelope value) {
-            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, Attribute.class));
+            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, AbstractAttribute.class));
         }
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
index 150a87b..b0b4a20 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
@@ -53,18 +53,6 @@
 
 import static java.util.logging.Logger.getLogger;
 
-// Branch-dependent imports
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.Operation;
-
 
 /**
  * Formats {@linkplain AbstractFeature features} or {@linkplain DefaultFeatureType feature types} in a tabular format.
@@ -162,7 +150,7 @@
 
     /**
      * Returns the type of objects formatted by this class. This method has to return {@code Object.class}
-     * since it is the only common parent to {@link Feature} and {@link FeatureType}.
+     * since it is the only common parent to {@code Feature} and {@link FeatureType}.
      *
      * @return {@code Object.class}
      */
@@ -229,20 +217,20 @@
     public enum Column {
         /**
          * Natural language designator for the property.
-         * This is the character sequence returned by {@link PropertyType#getDesignation()}.
+         * This is the character sequence returned by {@link AbstractIdentifiedType#getDesignation()}.
          * This column is omitted if no property has a designation.
          */
         DESIGNATION(Vocabulary.Keys.Designation),
 
         /**
          * Name of the property.
-         * This is the character sequence returned by {@link PropertyType#getName()}.
+         * This is the character sequence returned by {@link AbstractIdentifiedType#getName()}.
          */
         NAME(Vocabulary.Keys.Name),
 
         /**
-         * Type of property values. This is the type returned by {@link AttributeType#getValueClass()} or
-         * {@link FeatureAssociationRole#getValueType()}.
+         * Type of property values. This is the type returned by {@link DefaultAttributeType#getValueClass()} or
+         * {@link DefaultAssociationRole#getValueType()}.
          */
         TYPE(Vocabulary.Keys.Type),
 
@@ -250,21 +238,21 @@
          * Cardinality (for attributes) or multiplicity (for attribute types).
          * The cardinality is the actual number of attribute values.
          * The multiplicity is the minimum and maximum occurrences of attribute values.
-         * The multiplicity is made from the numbers returned by {@link AttributeType#getMinimumOccurs()}
-         * and {@link AttributeType#getMaximumOccurs()}.
+         * The multiplicity is made from the numbers returned by {@link DefaultAttributeType#getMinimumOccurs()}
+         * and {@link DefaultAttributeType#getMaximumOccurs()}.
          */
         CARDINALITY(Vocabulary.Keys.Cardinality),
 
         /**
          * Property value (for properties) or default value (for property types).
-         * This is the value returned by {@link Attribute#getValue()}, {@link FeatureAssociation#getValue()}
-         * or {@link AttributeType#getDefaultValue()}.
+         * This is the value returned by {@link AbstractAttribute#getValue()}, {@link AbstractAssociation#getValue()}
+         * or {@link DefaultAttributeType#getDefaultValue()}.
          */
         VALUE(Vocabulary.Keys.Value),
 
         /**
          * Other attributes that describes the attribute.
-         * This is made from the map returned by {@link Attribute#characteristics()}.
+         * This is made from the map returned by {@link AbstractAttribute#characteristics()}.
          * This column is omitted if no property has characteristics.
          */
         CHARACTERISTICS(Vocabulary.Keys.Characteristics),
@@ -301,8 +289,8 @@
      * The object may be an instance of any of the following types:
      *
      * <ul>
-     *   <li>{@link Feature}</li>
-     *   <li>{@link FeatureType}</li>
+     *   <li>{@code Feature}</li>
+     *   <li>{@code FeatureType}</li>
      * </ul>
      *
      * @throws IOException if an error occurred while writing to the given appendable.
@@ -315,13 +303,13 @@
         /*
          * Separate the Feature (optional) and the FeatureType (mandatory) instances.
          */
-        final FeatureType featureType;
-        final Feature feature;
-        if (object instanceof Feature) {
-            feature     = (Feature) object;
+        final DefaultFeatureType featureType;
+        final AbstractFeature feature;
+        if (object instanceof AbstractFeature) {
+            feature     = (AbstractFeature) object;
             featureType = feature.getType();
-        } else if (object instanceof FeatureType) {
-            featureType = (FeatureType) object;
+        } else if (object instanceof DefaultFeatureType) {
+            featureType = (DefaultFeatureType) object;
             feature     = null;
         } else {
             throw new IllegalArgumentException(Errors.getResources(displayLocale)
@@ -338,12 +326,12 @@
             boolean hasDesignation     = false;
             boolean hasCharacteristics = false;
             boolean hasDeprecatedTypes = false;
-            for (final PropertyType propertyType : featureType.getProperties(true)) {
+            for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
                 if (!hasDesignation) {
                     hasDesignation = propertyType.getDesignation() != null;
                 }
-                if (!hasCharacteristics && propertyType instanceof AttributeType<?>) {
-                    hasCharacteristics = !((AttributeType<?>) propertyType).characteristics().isEmpty();
+                if (!hasCharacteristics && propertyType instanceof DefaultAttributeType<?>) {
+                    hasCharacteristics = !((DefaultAttributeType<?>) propertyType).characteristics().isEmpty();
                 }
                 if (!hasDeprecatedTypes && propertyType instanceof Deprecable) {
                     hasDeprecatedTypes = ((Deprecable) propertyType).isDeprecated();
@@ -404,26 +392,26 @@
         final StringBuffer  buffer  = new StringBuffer();
         final FieldPosition dummyFP = new FieldPosition(-1);
         final List<String>  remarks = new ArrayList<>();
-        for (final PropertyType propertyType : featureType.getProperties(true)) {
+        for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
             Object value = null;
             int cardinality = -1;
             if (feature != null) {
-                if (!(propertyType instanceof AttributeType<?>) &&
-                    !(propertyType instanceof FeatureAssociationRole) &&
+                if (!(propertyType instanceof DefaultAttributeType<?>) &&
+                    !(propertyType instanceof DefaultAssociationRole) &&
                     !DefaultFeatureType.isParameterlessOperation(propertyType))
                 {
                     continue;
                 }
                 value = feature.getPropertyValue(propertyType.getName().toString());
                 if (value == null) {
-                    if (propertyType instanceof AttributeType<?>
-                            && ((AttributeType<?>) propertyType).getMinimumOccurs() == 0
-                            && ((AttributeType<?>) propertyType).characteristics().isEmpty())
+                    if (propertyType instanceof DefaultAttributeType<?>
+                            && ((DefaultAttributeType<?>) propertyType).getMinimumOccurs() == 0
+                            && ((DefaultAttributeType<?>) propertyType).characteristics().isEmpty())
                     {
                         continue;                           // If optional, no value and no characteristics, skip the full row.
                     }
-                    if (propertyType instanceof FeatureAssociationRole
-                            && ((FeatureAssociationRole) propertyType).getMinimumOccurs() == 0)
+                    if (propertyType instanceof DefaultAssociationRole
+                            && ((DefaultAssociationRole) propertyType).getMinimumOccurs() == 0)
                     {
                         continue;                           // If optional and no value, skip the full row.
                     }
@@ -433,16 +421,12 @@
                 } else {
                     cardinality = 1;
                 }
-            } else if (propertyType instanceof AttributeType<?>) {
-                value = ((AttributeType<?>) propertyType).getDefaultValue();
-            } else if (propertyType instanceof Operation) {
+            } else if (propertyType instanceof DefaultAttributeType<?>) {
+                value = ((DefaultAttributeType<?>) propertyType).getDefaultValue();
+            } else if (propertyType instanceof AbstractOperation) {
                 buffer.append(" = ");
                 try {
-                    if (propertyType instanceof AbstractOperation) {
-                        ((AbstractOperation) propertyType).formatResultFormula(buffer);
-                    } else {
-                        AbstractOperation.defaultFormula(((Operation) propertyType).getParameters(), buffer);
-                    }
+                    ((AbstractOperation) propertyType).formatResultFormula(buffer);
                 } catch (IOException e) {
                     throw new UncheckedIOException(e);      // Should never happen since we write in a StringBuffer.
                 }
@@ -452,25 +436,25 @@
             final String   valueType;                       // The value to write in the type column.
             final Class<?> valueClass;                      // AttributeType.getValueClass() if applicable.
             final int minimumOccurs, maximumOccurs;         // Negative values mean no cardinality.
-            final IdentifiedType resultType;                // Result of operation if applicable.
-            if (propertyType instanceof Operation) {
-                resultType = ((Operation) propertyType).getResult();                // May be null
+            final AbstractIdentifiedType resultType;        // Result of operation if applicable.
+            if (propertyType instanceof AbstractOperation) {
+                resultType = ((AbstractOperation) propertyType).getResult();        // May be null
             } else {
                 resultType = propertyType;
             }
-            if (resultType instanceof AttributeType<?>) {
-                final AttributeType<?> pt = (AttributeType<?>) resultType;
+            if (resultType instanceof DefaultAttributeType<?>) {
+                final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) resultType;
                 minimumOccurs = pt.getMinimumOccurs();
                 maximumOccurs = pt.getMaximumOccurs();
                 valueClass    = pt.getValueClass();
                 valueType     = getFormat(Class.class).format(valueClass, buffer, dummyFP).toString();
                 buffer.setLength(0);
-            } else if (resultType instanceof FeatureAssociationRole) {
-                final FeatureAssociationRole pt = (FeatureAssociationRole) resultType;
+            } else if (resultType instanceof DefaultAssociationRole) {
+                final DefaultAssociationRole pt = (DefaultAssociationRole) resultType;
                 minimumOccurs = pt.getMinimumOccurs();
                 maximumOccurs = pt.getMaximumOccurs();
                 valueType     = toString(DefaultAssociationRole.getValueTypeName(pt));
-                valueClass    = Feature.class;
+                valueClass    = AbstractFeature.class;
             } else {
                 valueType  = (resultType != null) ? toString(resultType.getName()) : "";
                 valueClass = null;
@@ -557,10 +541,10 @@
                         while (it.hasNext()) {
                             value = it.next();
                             if (value != null) {
-                                if (propertyType instanceof FeatureAssociationRole) {
-                                    final String p = DefaultAssociationRole.getTitleProperty((FeatureAssociationRole) propertyType);
+                                if (propertyType instanceof DefaultAssociationRole) {
+                                    final String p = DefaultAssociationRole.getTitleProperty((DefaultAssociationRole) propertyType);
                                     if (p != null) {
-                                        value = ((Feature) value).getPropertyValue(p);
+                                        value = ((AbstractFeature) value).getPropertyValue(p);
                                         if (value == null) continue;
                                     }
                                 } else if (format != null && valueClass.isInstance(value)) {    // Null safe because of getFormat(valueClass) contract.
@@ -614,10 +598,10 @@
                      * Characteristics are handled as "attributes of attributes".
                      */
                     case CHARACTERISTICS: {
-                        if (propertyType instanceof AttributeType<?>) {
+                        if (propertyType instanceof DefaultAttributeType<?>) {
                             int length = 0;
                             String separator = "";
-format:                     for (final AttributeType<?> ct : ((AttributeType<?>) propertyType).characteristics().values()) {
+format:                     for (final DefaultAttributeType<?> ct : ((DefaultAttributeType<?>) propertyType).characteristics().values()) {
                                 /*
                                  * Format the characteristic name. We will append the value(s) later.
                                  * We keep trace of the text length in order to stop formatting if the
@@ -635,9 +619,9 @@
                                      * given by the default value 'cv'.  Nevertheless we have to check if current
                                      * feature overrides this characteristic.
                                      */
-                                    final Property cp = feature.getProperty(propertyType.getName().toString());
-                                    if (cp instanceof Attribute<?>) {            // Should always be true, but we are paranoiac.
-                                        Attribute<?> ca = ((Attribute<?>) cp).characteristics().get(cn.toString());
+                                    final Object cp = feature.getProperty(propertyType.getName().toString());
+                                    if (cp instanceof AbstractAttribute<?>) {            // Should always be true, but we are paranoiac.
+                                        AbstractAttribute<?> ca = ((AbstractAttribute<?>) cp).characteristics().get(cn.toString());
                                         if (ca != null) cv = ca.getValues();
                                     }
                                 }
@@ -721,8 +705,8 @@
             text = ((InternationalString) value).toString(displayLocale);
         } else if (value instanceof GenericName) {
             text = toString((GenericName) value);
-        } else if (value instanceof IdentifiedType) {
-            text = toString(((IdentifiedType) value).getName());
+        } else if (value instanceof AbstractIdentifiedType) {
+            text = toString(((AbstractIdentifiedType) value).getName());
         } else if (value instanceof IdentifiedObject) {
             text = IdentifiedObjects.getIdentifierOrName((IdentifiedObject) value);
         } else {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
index 0d16889..261c8ef 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
@@ -27,11 +27,6 @@
 import org.apache.sis.util.collection.WeakHashSet;
 import org.apache.sis.util.resources.Errors;
 
-// Branch-dependent imports
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.FeatureAssociationRole;
-
 
 /**
  * A set of pre-defined operations expecting a {@code Feature} as input and producing an {@code Attribute} as output.
@@ -115,7 +110,7 @@
     /**
      * The pool of operations or operation dependencies created so far, for sharing exiting instances.
      */
-    static final WeakHashSet<PropertyType> POOL = new WeakHashSet<>(PropertyType.class);
+    static final WeakHashSet<AbstractIdentifiedType> POOL = new WeakHashSet<>(AbstractIdentifiedType.class);
 
     /**
      * Do not allow instantiation of this class.
@@ -152,13 +147,17 @@
      * identified by the {@code referent} argument, the returned property is writable if the referenced
      * property is also writable.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of {@code referent} parameter will be changed to {@code PropertyType}
+     * if and when such interface will be defined in GeoAPI.</div>
+     *
      * @param  identification  the name and other information to be given to the operation.
      * @param  referent        the referenced attribute or feature association.
      * @return an operation which is an alias for the {@code referent} property.
      *
      * @see Features#getLinkTarget(PropertyType)
      */
-    public static Operation link(final Map<String,?> identification, final PropertyType referent) {
+    public static AbstractOperation link(final Map<String,?> identification, final AbstractIdentifiedType referent) {
         ArgumentChecks.ensureNonNull("referent", referent);
         return POOL.unique(new LinkOperation(identification, referent));
     }
@@ -189,6 +188,10 @@
      * operation, the given string value will be split around the {@code delimiter} and each substring will be
      * forwarded to the corresponding single property.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of {@code singleAttributes} elements will be changed to {@code PropertyType}
+     * if and when such interface will be defined in GeoAPI.</div>
+     *
      * @param  identification    the name and other information to be given to the operation.
      * @param  delimiter         the characters to use as delimiter between each single property value.
      * @param  prefix            characters to use at the beginning of the concatenated string, or {@code null} if none.
@@ -203,8 +206,8 @@
      *
      * @see <a href="https://en.wikipedia.org/wiki/Compound_key">Compound key on Wikipedia</a>
      */
-    public static Operation compound(final Map<String,?> identification, final String delimiter,
-            final String prefix, final String suffix, final PropertyType... singleAttributes)
+    public static AbstractOperation compound(final Map<String,?> identification, final String delimiter,
+            final String prefix, final String suffix, final AbstractIdentifiedType... singleAttributes)
             throws UnconvertibleObjectException
     {
         ArgumentChecks.ensureNonEmpty("delimiter", delimiter);
@@ -215,8 +218,8 @@
         ArgumentChecks.ensureNonEmpty("singleAttributes", singleAttributes);
         if (singleAttributes.length == 1) {
             if ((prefix == null || prefix.isEmpty()) && (suffix == null || suffix.isEmpty())) {
-                final PropertyType at = singleAttributes[0];
-                if (!(at instanceof FeatureAssociationRole)) {
+                final AbstractIdentifiedType at = singleAttributes[0];
+                if (!(at instanceof DefaultAssociationRole)) {
                     return link(identification, at);
                 }
             }
@@ -249,6 +252,10 @@
      * This operation is read-only. Calls to {@code Attribute.setValue(Envelope)} will result in an
      * {@link IllegalStateException} to be thrown.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The type of {@code geometryAttributes} elements will be changed to {@code PropertyType}
+     * if and when such interface will be defined in GeoAPI.</div>
+     *
      * @param  identification      the name and other information to be given to the operation.
      * @param  crs                 the Coordinate Reference System in which to express the envelope, or {@code null}.
      * @param  geometryAttributes  the operation or attribute type from which to get geometry values.
@@ -256,8 +263,8 @@
      * @return an operation which will compute the envelope encompassing all geometries in the given attributes.
      * @throws FactoryException if a coordinate operation to the target CRS can not be created.
      */
-    public static Operation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs,
-            final PropertyType... geometryAttributes) throws FactoryException
+    public static AbstractOperation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs,
+            final AbstractIdentifiedType... geometryAttributes) throws FactoryException
     {
         ArgumentChecks.ensureNonNull("geometryAttributes", geometryAttributes);
         return POOL.unique(new EnvelopeOperation(identification, crs, geometryAttributes));
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureType.java
new file mode 100644
index 0000000..5f5e078
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureType.java
@@ -0,0 +1,41 @@
+/*
+ * 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.feature;
+
+import java.util.Collection;
+import org.opengis.util.GenericName;
+
+
+/**
+ * Place-holder for an interface not available in GeoAPI 3.0.
+ * This place-holder will be removed after we upgrade to a later GeoAPI version.
+ *
+ * <p><strong>Do not put this type in public API</strong>. We need to prevent users from using
+ * this type in order to reduce compatibility breaks when we will upgrade the GeoAPI version.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.5
+ * @version 0.5
+ * @module
+ */
+interface FeatureType {
+    GenericName getName();
+
+    Collection<AbstractIdentifiedType> getProperties(boolean includeSuperTypes);
+
+    boolean isAssignableFrom(DefaultFeatureType type);
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java b/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
index 0ecfd7c..59c7dc7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
@@ -22,7 +22,6 @@
 import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.quality.ConformanceResult;
 import org.opengis.metadata.quality.DataQuality;
 import org.opengis.metadata.quality.Element;
@@ -32,17 +31,6 @@
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.feature.Resources;
 
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Static methods working on features or attributes.
@@ -75,7 +63,7 @@
      * @category verification
      */
     @SuppressWarnings("unchecked")
-    public static <V> AttributeType<V> cast(final AttributeType<?> type, final Class<V> valueClass)
+    public static <V> DefaultAttributeType<V> cast(final DefaultAttributeType<?> type, final Class<V> valueClass)
             throws ClassCastException
     {
         if (type != null) {
@@ -89,7 +77,7 @@
                         type.getName(), valueClass, actual));
             }
         }
-        return (AttributeType<V>) type;
+        return (DefaultAttributeType<V>) type;
     }
 
     /**
@@ -106,7 +94,7 @@
      * @category verification
      */
     @SuppressWarnings("unchecked")
-    public static <V> Attribute<V> cast(final Attribute<?> attribute, final Class<V> valueClass)
+    public static <V> AbstractAttribute<V> cast(final AbstractAttribute<?> attribute, final Class<V> valueClass)
             throws ClassCastException
     {
         if (attribute != null) {
@@ -120,18 +108,18 @@
                         attribute.getName(), valueClass, actual));
             }
         }
-        return (Attribute<V>) attribute;
+        return (AbstractAttribute<V>) attribute;
     }
 
     /**
-     * Returns the given type as an {@link AttributeType} by casting if possible, or by getting the result type
+     * Returns the given type as an {@code AttributeType} by casting if possible, or by getting the result type
      * of an operation. More specifically this method returns the first of the following types which apply:
      *
      * <ul>
-     *   <li>If the given type is an instance of {@link AttributeType}, then it is returned as-is.</li>
-     *   <li>If the given type is an instance of {@link Operation} and the {@linkplain Operation#getResult()
-     *       result type} is an {@link AttributeType}, then that result type is returned.</li>
-     *   <li>If the given type is an instance of {@link Operation} and the {@linkplain Operation#getResult()
+     *   <li>If the given type is an instance of {@code AttributeType}, then it is returned as-is.</li>
+     *   <li>If the given type is an instance of {@code Operation} and the {@code Operation.getResult()
+     *       result type} is an {@code AttributeType}, then that result type is returned.</li>
+     *   <li>If the given type is an instance of {@code Operation} and the {@code Operation.getResult()
      *       result type} is another operation, then the above check is performed recursively.</li>
      * </ul>
      *
@@ -140,14 +128,14 @@
      *
      * @since 1.1
      */
-    public static Optional<AttributeType<?>> toAttribute(IdentifiedType type) {
-        if (!(type instanceof AttributeType<?>)) {
-            if (!(type instanceof Operation)) {
+    public static Optional<DefaultAttributeType<?>> toAttribute(AbstractIdentifiedType type) {
+        if (!(type instanceof DefaultAttributeType<?>)) {
+            if (!(type instanceof AbstractOperation)) {
                 return Optional.empty();
             }
-            type = ((Operation) type).getResult();
-            if (!(type instanceof AttributeType<?>)) {
-                if (!(type instanceof Operation)) {
+            type = ((AbstractOperation) type).getResult();
+            if (!(type instanceof DefaultAttributeType<?>)) {
+                if (!(type instanceof AbstractOperation)) {
                     return Optional.empty();
                 }
                 /*
@@ -155,15 +143,15 @@
                  * contain a cycle. However given that the consequence of an infinite cycle here
                  * would be thread freeze, we check as a safety.
                  */
-                final Map<IdentifiedType,Boolean> done = new IdentityHashMap<>(4);
-                while (!((type = ((Operation) type).getResult()) instanceof AttributeType<?>)) {
-                    if (!(type instanceof Operation) || done.put(type, Boolean.TRUE) != null) {
+                final Map<AbstractIdentifiedType,Boolean> done = new IdentityHashMap<>(4);
+                while (!((type = ((AbstractOperation) type).getResult()) instanceof DefaultAttributeType<?>)) {
+                    if (!(type instanceof AbstractOperation) || done.put(type, Boolean.TRUE) != null) {
                         return Optional.empty();
                     }
                 }
             }
         }
-        return Optional.of((AttributeType<?>) type);
+        return Optional.of((DefaultAttributeType<?>) type);
     }
 
     /**
@@ -175,62 +163,33 @@
      * @param  types  types for which to find a common type, or {@code null}.
      * @return a feature type which is assignable from all given types, or {@code null} if none.
      *
-     * @see FeatureType#isAssignableFrom(FeatureType)
+     * @see DefaultFeatureType#isAssignableFrom(DefaultFeatureType)
      *
      * @since 1.0
      */
-    public static FeatureType findCommonParent(final Iterable<? extends FeatureType> types) {
+    public static DefaultFeatureType findCommonParent(final Iterable<? extends DefaultFeatureType> types) {
         return (types != null) ? CommonParentFinder.select(types) : null;
     }
 
-    /**
-     * Returns the type of values provided by the given property. For {@linkplain AttributeType attributes}
-     * (which is the most common case), the value type is given by {@link AttributeType#getValueClass()}.
-     * For {@linkplain FeatureAssociationRole feature associations}, the value type is {@link Feature}.
-     * For {@linkplain Operation operations}, the value type is determined recursively from the
-     * {@linkplain Operation#getResult() operation result}.
-     * If the value type can not be determined, then this method returns {@code null}.
+    /*
+     * Following method is omitted on master because it depends on GeoAPI interfaces not yet published:
      *
-     * @param  type  the property for which to get the type of values, or {@code null}.
-     * @return the type of values provided by the given property, or {@code null} if unknown.
-     *
-     * @see AttributeType#getValueClass()
-     *
-     * @since 1.0
+     *     public static Class<?> getValueClass(PropertyType type)
      */
-    public static Class<?> getValueClass(PropertyType type) {
-        while (type instanceof Operation) {
-            final IdentifiedType result = ((Operation) type).getResult();
-            if (result != type && result instanceof PropertyType) {
-                type = (PropertyType) result;
-            } else if (result instanceof FeatureType) {
-                return Feature.class;
-            } else {
-                break;
-            }
-        }
-        if (type instanceof AttributeType<?>) {
-            return ((AttributeType<?>) type).getValueClass();
-        } else if (type instanceof FeatureAssociationRole) {
-            return Feature.class;
-        } else {
-            return null;
-        }
-    }
 
     /**
      * Returns the name of the type of values that the given property can take.
-     * The type of value can be a {@link Class}, a {@link org.opengis.feature.FeatureType}
+     * The type of value can be a {@link Class}, a {@code FeatureType}
      * or another {@code PropertyType} depending on given argument:
      *
      * <ul>
-     *   <li>If {@code property} is an {@link AttributeType}, then this method gets the
+     *   <li>If {@code property} is an {@code AttributeType}, then this method gets the
      *       {@linkplain DefaultAttributeType#getValueClass() value class} and
      *       {@linkplain DefaultNameFactory#toTypeName(Class) maps that class to a name}.</li>
-     *   <li>If {@code property} is a {@link FeatureAssociationRole}, then this method gets
+     *   <li>If {@code property} is a {@code FeatureAssociationRole}, then this method gets
      *       the name of the {@linkplain DefaultAssociationRole#getValueType() value type}.
      *       This methods can work even if the associated {@code FeatureType} is not yet resolved.</li>
-     *   <li>If {@code property} is an {@link Operation}, then this method returns the name of the
+     *   <li>If {@code property} is an {@code Operation}, then this method returns the name of the
      *       {@linkplain AbstractOperation#getResult() result type}.</li>
      * </ul>
      *
@@ -239,15 +198,15 @@
      *
      * @since 0.8
      */
-    public static GenericName getValueTypeName(final PropertyType property) {
-        if (property instanceof FeatureAssociationRole) {
+    public static GenericName getValueTypeName(final AbstractIdentifiedType property) {
+        if (property instanceof DefaultAssociationRole) {
             // Tested first because this is the main interest for this method.
-            return DefaultAssociationRole.getValueTypeName((FeatureAssociationRole) property);
-        } else if (property instanceof AttributeType<?>) {
+            return DefaultAssociationRole.getValueTypeName((DefaultAssociationRole) property);
+        } else if (property instanceof DefaultAttributeType<?>) {
             final DefaultNameFactory factory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
-            return factory.toTypeName(((AttributeType<?>) property).getValueClass());
-        } else if (property instanceof Operation) {
-            final IdentifiedType result = ((Operation) property).getResult();
+            return factory.toTypeName(((DefaultAttributeType<?>) property).getValueClass());
+        } else if (property instanceof AbstractOperation) {
+            final AbstractIdentifiedType result = ((AbstractOperation) property).getResult();
             if (result != null) {
                 return result.getName();
             }
@@ -258,7 +217,7 @@
     /**
      * If the given property is a link, returns the name of the referenced property.
      * A link is an operation created by a call to {@link FeatureOperations#link(Map, PropertyType)},
-     * in which case the value returned by this method is the name of the {@link PropertyType} argument
+     * in which case the value returned by this method is the name of the {@code PropertyType} argument
      * which has been given to that {@code link(…)} method.
      *
      * @param  property  the property to test, or {@code null} if none.
@@ -268,7 +227,7 @@
      *
      * @since 1.1
      */
-    public static Optional<String> getLinkTarget(final PropertyType property) {
+    public static Optional<String> getLinkTarget(final AbstractIdentifiedType property) {
         if (property instanceof LinkOperation) {
             return Optional.of(((LinkOperation) property).referentName);
         }
@@ -289,24 +248,13 @@
      * {@code InvalidPropertyValueException} is thrown. Otherwise this method returns doing nothing.
      *
      * @param  feature  the feature to validate, or {@code null}.
-     * @throws InvalidPropertyValueException if the given feature is non-null and does not pass validation.
+     * @throws IllegalArgumentException if the given feature is non-null and does not pass validation.
      *
      * @since 0.7
      */
-    public static void validate(final Feature feature) throws InvalidPropertyValueException {
+    public static void validate(final AbstractFeature feature) throws IllegalArgumentException {
         if (feature != null) {
-            /*
-             * Delegate to AbstractFeature.quality() if possible because the user may have overridden the method.
-             * Otherwise fallback on the same code than AbstractFeature.quality() default implementation.
-             */
-            final DataQuality quality;
-            if (feature instanceof AbstractFeature) {
-                quality = ((AbstractFeature) feature).quality();
-            } else {
-                final Validator v = new Validator(ScopeCode.FEATURE);
-                v.validate(feature.getType(), feature);
-                quality = v.quality;
-            }
+            final DataQuality quality = feature.quality();
             /*
              * Loop on quality elements and check conformance results.
              * NOTE: other types of result are ignored for now, since those other
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java b/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java
index 9a6e94f..df704ee 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java
@@ -20,12 +20,6 @@
 import java.util.Iterator;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
-
-// Branch-dependent imports
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.MultiValuedPropertyException;
-import org.opengis.feature.InvalidPropertyValueException;
 import org.apache.sis.util.Deprecable;
 
 
@@ -38,7 +32,7 @@
  * @since   0.5
  * @module
  */
-abstract class Field<V> implements Property {
+abstract class Field<V> extends Property {
     /**
      * For subclass constructors.
      */
@@ -60,12 +54,12 @@
      * Returns the field feature or attribute value, or {@code null} if none.
      *
      * @return the feature or attribute value (may be {@code null}).
-     * @throws MultiValuedPropertyException if this field contains more than one value.
+     * @throws IllegalStateException if this field contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract V getValue() throws MultiValuedPropertyException;
+    public abstract V getValue() throws IllegalStateException;
 
     /**
      * Returns all features or attribute values, or an empty collection if none.
@@ -94,16 +88,16 @@
      * then delegates to {@link #setValue(Object)}.</p>
      *
      * @param  values  the new values.
-     * @throws InvalidPropertyValueException if the given collection contains too many elements.
+     * @throws IllegalArgumentException if the given collection contains too many elements.
      */
-    public void setValues(final Collection<? extends V> values) throws InvalidPropertyValueException {
+    public void setValues(final Collection<? extends V> values) throws IllegalArgumentException {
         V value = null;
         ArgumentChecks.ensureNonNull("values", values);
         final Iterator<? extends V> it = values.iterator();
         if (it.hasNext()) {
             value = it.next();
             if (it.hasNext()) {
-                throw new InvalidPropertyValueException(Errors.format(Errors.Keys.TooManyOccurrences_2, 1, getName()));
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.TooManyOccurrences_2, 1, getName()));
             }
         }
         setValue(value);
@@ -112,7 +106,7 @@
     /**
      * Returns whether the given property is deprecated.
      */
-    static boolean isDeprecated(final PropertyType type) {
+    static boolean isDeprecated(final AbstractIdentifiedType type) {
         return (type instanceof Deprecable) && ((Deprecable) type).isDeprecated();
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java
index 022a8bd..68adf1e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java
@@ -21,9 +21,6 @@
 import org.opengis.util.GenericName;
 import org.apache.sis.util.resources.Errors;
 
-// Branch-dependent imports
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Base class of property types having a value and a multiplicity.
@@ -39,7 +36,7 @@
  * @since   0.5
  * @module
  */
-abstract class FieldType extends AbstractIdentifiedType implements PropertyType {
+abstract class FieldType extends AbstractIdentifiedType {
     /**
      * For cross-version compatibility.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/InvalidFeatureException.java b/core/sis-feature/src/main/java/org/apache/sis/feature/InvalidFeatureException.java
index 211a3c8..a97c101 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/InvalidFeatureException.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/InvalidFeatureException.java
@@ -19,27 +19,19 @@
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.LocalizedException;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * Thrown when a feature fails at least one conformance test.
  *
- * <div class="note"><b>Note:</b>
- * this exception extends {@link InvalidPropertyValueException} because an Apache SIS feature
- * can be invalid only if a property is invalid.</div>
- *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  *
- * @see Features#validate(Feature)
+ * @see Features#validate(AbstractFeature)
  *
  * @since 0.7
  * @module
  */
-final class InvalidFeatureException extends InvalidPropertyValueException implements LocalizedException {
+final class InvalidFeatureException extends IllegalArgumentException implements LocalizedException {
     /**
      * For cross-version compatibility.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
index e009f39..e957e00 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
@@ -26,12 +26,6 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * A link operation, which is like a redirection or an alias.
@@ -56,7 +50,7 @@
     /**
      * The type of the result.
      */
-    private final PropertyType result;
+    private final AbstractIdentifiedType result;
 
     /**
      * The name of the referenced attribute or feature association.
@@ -68,10 +62,8 @@
      *
      * @param identification  the name of the link, together with optional information.
      * @param referent        the referenced attribute or feature association.
-     *
-     * @see FeatureOperations#link(Map, PropertyType)
      */
-    LinkOperation(final Map<String,?> identification, PropertyType referent) {
+    LinkOperation(final Map<String,?> identification, AbstractIdentifiedType referent) {
         super(identification);
         if (referent instanceof LinkOperation) {
             referent = ((LinkOperation) referent).result;
@@ -96,7 +88,7 @@
      * Returns the expected result type.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return result;
     }
 
@@ -116,7 +108,7 @@
      * @return the linked property from the given feature.
      */
     @Override
-    public Property apply(final Feature feature, final ParameterValueGroup parameters) {
+    public Object apply(final AbstractFeature feature, final ParameterValueGroup parameters) {
         ArgumentChecks.ensureNonNull("feature", feature);
         return feature.getProperty(referentName);
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java
index ebfe2aa..c6867da 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java
@@ -21,12 +21,6 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.feature.Resources;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.MultiValuedPropertyException;
-
 
 /**
  * An instance of an {@linkplain DefaultAssociationRole association role} containing an arbitrary amount of values.
@@ -60,16 +54,16 @@
     /**
      * The association values.
      */
-    private CheckedArrayList<Feature> values;
+    private CheckedArrayList<AbstractFeature> values;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role Information about the association.
      */
-    public MultiValuedAssociation(final FeatureAssociationRole role) {
+    public MultiValuedAssociation(final DefaultAssociationRole role) {
         super(role);
-        values = new CheckedArrayList<>(Feature.class);
+        values = new CheckedArrayList<>(AbstractFeature.class);
     }
 
     /**
@@ -78,12 +72,12 @@
      * @param role   Information about the association.
      * @param values The initial values, or {@code null} for initializing to an empty list.
      */
-    MultiValuedAssociation(final FeatureAssociationRole role, final Object values) {
+    MultiValuedAssociation(final DefaultAssociationRole role, final Object values) {
         super(role);
         if (values == null) {
-            this.values = new CheckedArrayList<>(Feature.class);
+            this.values = new CheckedArrayList<>(AbstractFeature.class);
         } else {
-            this.values = CheckedArrayList.castOrCopy((CheckedArrayList<?>) values, Feature.class);
+            this.values = CheckedArrayList.castOrCopy((CheckedArrayList<?>) values, AbstractFeature.class);
         }
     }
 
@@ -91,14 +85,14 @@
      * Returns the feature, or {@code null} if none.
      *
      * @return the feature (may be {@code null}).
-     * @throws MultiValuedPropertyException if this association contains more than one value.
+     * @throws IllegalStateException if this association contains more than one value.
      */
     @Override
-    public Feature getValue() {
+    public AbstractFeature getValue() {
         switch (values.size()) {
             case 0:  return null;
             case 1:  return values.get(0);
-            default: throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
+            default: throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
         }
     }
 
@@ -111,7 +105,7 @@
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Collection<Feature> getValues() {
+    public Collection<AbstractFeature> getValues() {
         return values;
     }
 
@@ -121,7 +115,7 @@
      * @param  value  the new value, or {@code null} for removing all values from this association.
      */
     @Override
-    public void setValue(final Feature value) {
+    public void setValue(final AbstractFeature value) {
         values.clear();
         if (value != null) {
             ensureValid(role.getValueType(), value.getType());
@@ -135,12 +129,12 @@
      * @param  newValues  the new values.
      */
     @Override
-    public void setValues(final Collection<? extends Feature> newValues) {
+    public void setValues(final Collection<? extends AbstractFeature> newValues) {
         if (newValues != values) {
             ArgumentChecks.ensureNonNull("values", newValues);  // The parameter name in public API is "values".
-            final FeatureType base = role.getValueType();
+            final DefaultFeatureType base = role.getValueType();
             values.clear();
-            for (final Feature value : newValues) {
+            for (final AbstractFeature value : newValues) {
                 ensureValid(base, value.getType());
                 values.add(value);
             }
@@ -159,7 +153,7 @@
     @SuppressWarnings("unchecked")
     public MultiValuedAssociation clone() throws CloneNotSupportedException {
         final MultiValuedAssociation clone = (MultiValuedAssociation) super.clone();
-        clone.values = (CheckedArrayList<Feature>) clone.values.clone();
+        clone.values = (CheckedArrayList<AbstractFeature>) clone.values.clone();
         return clone;
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java b/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
index cf61e49..6d34b5e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
@@ -23,10 +23,6 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.feature.Resources;
 
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.MultiValuedPropertyException;
-
 
 /**
  * An instance of an {@linkplain DefaultAttributeType attribute type} containing an arbitrary amount of values.
@@ -73,7 +69,7 @@
      *
      * @param  type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    public MultiValuedAttribute(final AttributeType<V> type) {
+    public MultiValuedAttribute(final DefaultAttributeType<V> type) {
         super(type);
         values = new CheckedArrayList<>(type.getValueClass());
         final V value = type.getDefaultValue();
@@ -90,7 +86,7 @@
      * @param  values  the initial values, or {@code null} for initializing to an empty list.
      */
     @SuppressWarnings("unchecked")
-    MultiValuedAttribute(final AttributeType<V> type, final Object values) {
+    MultiValuedAttribute(final DefaultAttributeType<V> type, final Object values) {
         super(type);
         final Class<V> valueClass = type.getValueClass();
         if (values == null) {
@@ -109,14 +105,14 @@
      * Returns the attribute value, or {@code null} if none.
      *
      * @return the attribute value (may be {@code null}).
-     * @throws MultiValuedPropertyException if this attribute contains more than one value.
+     * @throws IllegalStateException if this attribute contains more than one value.
      */
     @Override
     public V getValue() {
         switch (values.size()) {
             case 0:  return null;
             case 1:  return values.get(0);
-            default: throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
+            default: throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
         }
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
index 0fa1555..9fe5fcc 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
@@ -16,19 +16,11 @@
  */
 package org.apache.sis.feature;
 
-import java.util.Set;
 import java.util.Collection;
 import java.util.Collections;
 import java.io.Serializable;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.feature.FeatureInstantiationException;
 import org.opengis.util.GenericName;
-import org.opengis.util.InternationalString;
 import org.apache.sis.internal.util.Strings;
-import org.apache.sis.internal.feature.Resources;
 
 
 /**
@@ -76,51 +68,10 @@
     }
 
     /**
-     * Undefined.
-     */
-    @Override
-    public InternationalString getDefinition() {
-        return null;
-    }
-
-    /**
-     * Declares that this feature shall not be instantiated.
-     */
-    @Override
-    public boolean isAbstract() {
-        return true;
-    }
-
-    /**
-     * Conservatively assumes that the feature is not simple,
-     * since we do not know what the actual feature will be.
-     */
-    @Override
-    public boolean isSimple() {
-        return false;
-    }
-
-    /**
-     * Always throws {@link PropertyNotFoundException} since this feature type has no declared property yet.
-     */
-    @Override
-    public PropertyType getProperty(final String name) throws PropertyNotFoundException {
-        throw new PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
-    }
-
-    /**
      * Returns an empty set since this feature has no declared property yet.
      */
     @Override
-    public Collection<? extends PropertyType> getProperties(final boolean includeSuperTypes) {
-        return Collections.emptySet();
-    }
-
-    /**
-     * Returns an empty set since this feature has no declared parent yet.
-     */
-    @Override
-    public Set<? extends FeatureType> getSuperTypes() {
+    public Collection<AbstractIdentifiedType> getProperties(final boolean includeSuperTypes) {
         return Collections.emptySet();
     }
 
@@ -128,26 +79,8 @@
      * This feature type is considered independent of all other feature types except itself.
      */
     @Override
-    public boolean isAssignableFrom(FeatureType type) {
-        if (type == this) {
-            return true;
-        }
-        if (type instanceof NamedFeatureType) {
-            type = ((NamedFeatureType) type).resolved;
-        }
-        if (type == null) {
-            return false;
-        }
-        final FeatureType resolved = this.resolved;
-        return (resolved != null) && resolved.isAssignableFrom(type);
-    }
-
-    /**
-     * Unsupported operation, since the feature has not yet been resolved.
-     */
-    @Override
-    public Feature newInstance() throws FeatureInstantiationException {
-        throw new FeatureInstantiationException(Resources.format(Resources.Keys.UnresolvedFeatureName_1, getName()));
+    public boolean isAssignableFrom(final DefaultFeatureType type) {
+        return false;
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/Property.java b/core/sis-feature/src/main/java/org/apache/sis/feature/Property.java
new file mode 100644
index 0000000..00b4ff3
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/Property.java
@@ -0,0 +1,38 @@
+/*
+ * 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.feature;
+
+import org.opengis.util.GenericName;
+
+
+/**
+ * Place-holder for an interface not available in GeoAPI 3.0.
+ * This place-holder will be removed after we upgrade to a later GeoAPI version.
+ *
+ * <p><strong>Do not put this type in public API</strong>. We need to prevent users from using
+ * this type in order to reduce compatibility breaks when we will upgrade the GeoAPI version.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.5
+ * @version 0.5
+ * @module
+ */
+abstract class Property {
+    public abstract GenericName getName();
+
+    public abstract Object getValue();
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/PropertyView.java b/core/sis-feature/src/main/java/org/apache/sis/feature/PropertyView.java
index c8e414b..bffabc3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/PropertyView.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/PropertyView.java
@@ -21,21 +21,12 @@
 import java.util.Iterator;
 import java.util.Collection;
 import java.util.Collections;
-import java.io.Serializable;
+import org.opengis.util.GenericName;
 import org.apache.sis.util.collection.CheckedContainer;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Classes;
 import org.apache.sis.internal.feature.Resources;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.Property;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.MultiValuedPropertyException;
-
 
 /**
  * An attribute or association implementation which delegate its work to the parent feature.
@@ -51,32 +42,11 @@
  * @since   0.8
  * @module
  */
-abstract class PropertyView<V> extends Field<V> implements Property, Serializable {
+final class PropertyView {
     /**
-     * For cross-version compatibility.
+     * Do not allow instantiation of this class.
      */
-    private static final long serialVersionUID = -5605415150581699255L;
-
-    /**
-     * The feature from which to read and where to write the attribute or association value.
-     */
-    final Feature feature;
-
-    /**
-     * The string representation of the property name. This is the value to be given in calls to
-     * {@link Feature#getPropertyValue(String)} and {@link Feature#setPropertyValue(String, Object)}.
-     */
-    final String name;
-
-    /**
-     * Creates a new property which will delegate its work to the given feature.
-     *
-     * @param feature  the feature from which to read and where to write the property value.
-     * @param name     the string representation of the property name.
-     */
-    PropertyView(final Feature feature, final String name) {
-        this.feature = feature;
-        this.name = name;
+    private PropertyView() {
     }
 
     /**
@@ -86,32 +56,26 @@
      * @param type     the type of the property. Must be one of the properties listed in the
      *                 {@code feature} (this is not verified by this constructor).
      */
-    static Property create(final Feature feature, final PropertyType type) {
-        if (type instanceof AttributeType<?>) {
-            return AttributeView.create(feature, (AttributeType<?>) type);
-        } else if (type instanceof FeatureAssociationRole) {
-            return AssociationView.create(feature, (FeatureAssociationRole) type);
-        } else if (type instanceof Operation) {
-            return ((Operation) type).apply(feature, null);
+    static Property create(final AbstractFeature feature, final AbstractIdentifiedType type) {
+        if (type instanceof DefaultAttributeType<?>) {
+            return AttributeView.create(feature, (DefaultAttributeType<?>) type);
+        } else if (type instanceof DefaultAssociationRole) {
+            return AssociationView.create(feature, (DefaultAssociationRole) type);
+        } else if (type instanceof AbstractOperation) {
+            return (Property) ((AbstractOperation) type).apply(feature, null);
         } else {
             throw new IllegalArgumentException(Errors.format(Errors.Keys.UnknownType_1, Classes.getClass(type)));
         }
     }
 
     /**
-     * Returns the class of values.
-     */
-    abstract Class<V> getValueClass();
-
-    /**
      * Returns the singleton value. This default implementation assumes that the property is multi-valued
      * (single-valued properties shall override this method), but we nevertheless provide a fallback for
      * non-{@code Iterable} values as a safety against implementations that are not strictly compliant
-     * to our {@link Feature#getPropertyValue(String)} method contract. Then this method verifies that
+     * to our {@code Feature.getPropertyValue(String)} method contract. Then this method verifies that
      * the value is a collection containing zero or one element and returns that element or {@code null}.
      */
-    @Override
-    public V getValue() throws MultiValuedPropertyException {
+    static Object getValue(final AbstractFeature feature, final String name) {
         Object value = feature.getPropertyValue(name);
         if (value instanceof Iterable<?>) {
             final Iterator<?> it = ((Iterable<?>) value).iterator();
@@ -120,19 +84,18 @@
             }
             value = it.next();
             if (it.hasNext()) {
-                throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, name));
+                throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, name));
             }
         }
-        return getValueClass().cast(value);
+        return value;
     }
 
     /**
      * Sets the values of the given attribute. This default implementation assumes that the property
      * is multi-valued (single-valued properties shall override this method) and that the
-     * {@link Feature#setPropertyValue(String, Object)} implementation will verify the argument type.
+     * {@code Feature.setPropertyValue(String, Object)} implementation will verify the argument type.
      */
-    @Override
-    public void setValue(final V value) {
+    static void setValue(final AbstractFeature feature, final String name, final Object value) {
         feature.setPropertyValue(name, singletonOrEmpty(value));
     }
 
@@ -152,13 +115,11 @@
      * contains elements of the expected type, but this verification is not always possible.
      * Consequently this method may, sometime, be actually unsafe.
      */
-    @Override
     @SuppressWarnings("unchecked")              // Actually not 100% safe, but we have done our best.
-    public Collection<V> getValues() {
+    static <V> Collection<V> getValues(final AbstractFeature feature, final String name, final Class<V> expected) {
         final Object values = feature.getPropertyValue(name);
         if (values instanceof Collection<?>) {
             if (values instanceof CheckedContainer<?>) {
-                final Class<?> expected = getValueClass();
                 final Class<?> actual = ((CheckedContainer<?>) values).getElementType();
                 if (expected != actual) {       // Really exact match, not Class.isAssignableFrom(Class).
                     throw new ClassCastException(Errors.format(Errors.Keys.UnexpectedTypeForReference_3, name, expected, actual));
@@ -166,49 +127,31 @@
             }
             return (Collection<V>) values;
         } else {
-            return singletonOrEmpty(getValueClass().cast(values));
+            return singletonOrEmpty(expected.cast(values));
         }
     }
 
     /**
      * Sets the values of the given attribute. This method assumes that the
-     * {@link Feature#setPropertyValue(String, Object)} implementation will
+     * {@code Feature.setPropertyValue(String, Object)} implementation will
      * verify the argument type.
      */
-    @Override
-    public final void setValues(final Collection<? extends V> values) {
+    static void setValues(final AbstractFeature feature, final String name, final Collection<?> values) {
         feature.setPropertyValue(name, values);
     }
 
     /**
      * Returns a hash code value for this property.
      */
-    @Override
-    public final int hashCode() {
+    static int hashCode(final AbstractFeature feature, final String name) {
         return Objects.hashCode(name) ^ System.identityHashCode(feature);
     }
 
     /**
-     * Compares this attribute with the given object for equality.
-     */
-    @Override
-    public final boolean equals(final Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj != null && obj.getClass() == getClass()) {
-            final PropertyView<?> that = (PropertyView<?>) obj;
-            return feature == that.feature && Objects.equals(name, that.name);
-        }
-        return false;
-    }
-
-    /**
      * Returns a string representation of this property for debugging purposes.
      */
-    @Override
-    public final String toString() {
-        return FieldType.toString(false, getClass().getSimpleName(), getName(),
-                Classes.getShortName(getValueClass()), getValues().iterator()).toString();
+    static String toString(final Class<?> classe, final Class<?> valueClass, final GenericName name, final Collection<?> values) {
+        return FieldType.toString(false, classe.getSimpleName(), name,
+                Classes.getShortName(valueClass), values.iterator()).toString();
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java
index 38b4138..0d73ae1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java
@@ -18,11 +18,6 @@
 
 import java.util.Objects;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * An instance of an {@linkplain DefaultAssociationRole association role} containing at most one value.
@@ -54,14 +49,14 @@
     /**
      * The associated feature.
      */
-    private Feature value;
+    private AbstractFeature value;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role  information about the association.
      */
-    public SingletonAssociation(final FeatureAssociationRole role) {
+    public SingletonAssociation(final DefaultAssociationRole role) {
         super(role);
         assert isSingleton(role.getMaximumOccurs());
     }
@@ -72,7 +67,7 @@
      * @param role   information about the association.
      * @param value  the initial value (may be {@code null}).
      */
-    SingletonAssociation(final FeatureAssociationRole role, final Feature value) {
+    SingletonAssociation(final DefaultAssociationRole role, final AbstractFeature value) {
         super(role);
         assert isSingleton(role.getMaximumOccurs());
         this.value = value;
@@ -87,7 +82,7 @@
      * @return the associated feature (may be {@code null}).
      */
     @Override
-    public Feature getValue() {
+    public AbstractFeature getValue() {
         return value;
     }
 
@@ -95,10 +90,10 @@
      * Sets the associated feature.
      *
      * @param  value  the new value, or {@code null}.
-     * @throws InvalidPropertyValueException if the given feature is not valid for this association.
+     * @throws IllegalArgumentException if the given feature is not valid for this association.
      */
     @Override
-    public void setValue(final Feature value) throws InvalidPropertyValueException {
+    public void setValue(final AbstractFeature value) throws IllegalArgumentException {
         if (value != null) {
             ensureValid(role.getValueType(), value.getType());
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java b/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
index af6028c..15b6f34 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
@@ -19,7 +19,6 @@
 import java.util.Objects;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
 
 
 /**
@@ -65,7 +64,7 @@
      *
      * @param type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    public SingletonAttribute(final AttributeType<V> type) {
+    public SingletonAttribute(final DefaultAttributeType<V> type) {
         super(type);
         assert isSingleton(type.getMaximumOccurs());
         value = type.getDefaultValue();
@@ -78,7 +77,7 @@
      * @param type   information about the attribute (base Java class, domain of values, <i>etc.</i>).
      * @param value  the initial value (may be {@code null}).
      */
-    SingletonAttribute(final AttributeType<V> type, final Object value) {
+    SingletonAttribute(final DefaultAttributeType<V> type, final Object value) {
         super(type);
         assert isSingleton(type.getMaximumOccurs());
         this.value = type.getValueClass().cast(value);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java b/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
index 0d3e38c..2426dee 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
@@ -26,12 +26,6 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CorruptedObjectException;
 
-// Branch-dependent imports
-import org.opengis.feature.Property;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * A feature in which only a small fraction of properties are expected to be provided. This implementation uses
@@ -120,14 +114,14 @@
      * @param  name  the property name.
      * @return the index for the property of the given name,
      *         or a negative value if the property is a parameterless operation.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
-    private Integer getIndex(final String name) throws PropertyNotFoundException {
+    private Integer getIndex(final String name) throws IllegalArgumentException {
         final Integer index = indices.get(name);
         if (index != null) {
             return index;
         }
-        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
+        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -172,10 +166,10 @@
      *
      * @param  name  the property name.
      * @return the property of the given name.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
     @Override
-    public Property getProperty(final String name) throws PropertyNotFoundException {
+    public Object getProperty(final String name) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("name", name);
         requireMapOfProperties();
         return getPropertyInstance(name);
@@ -185,11 +179,11 @@
      * Implementation of {@link #getProperty(String)} invoked when we know that the {@link #properties}
      * map contains {@code Property} instances (as opposed to their value).
      */
-    private Property getPropertyInstance(final String name) throws PropertyNotFoundException {
+    private Property getPropertyInstance(final String name) throws IllegalArgumentException {
         assert valuesKind == PROPERTIES : valuesKind;
         final Integer index = getIndex(name);
         if (index < 0) {
-            return getOperationResult(name);
+            return (Property) getOperationResult(name);
         }
         Property property = (Property) properties.get(index);
         if (property == null) {
@@ -207,10 +201,10 @@
      *         known to this feature, or if the property can not be set for another reason.
      */
     @Override
-    public void setProperty(final Property property) throws IllegalArgumentException {
+    public void setProperty(final Object property) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("property", property);
-        final String name = property.getName().toString();
-        verifyPropertyType(name, property);
+        final String name = ((Property) property).getName().toString();
+        verifyPropertyType(name, (Property) property);
         requireMapOfProperties();
         /*
          * Following index should never be OPERATION_INDEX (a negative value) because the call
@@ -224,13 +218,13 @@
      *
      * @param  name  the property name.
      * @return the value for the given property, or {@code null} if none.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      */
     @Override
-    public Object getPropertyValue(final String name) throws PropertyNotFoundException {
+    public Object getPropertyValue(final String name) throws IllegalArgumentException {
         final Object value = getValueOrFallback(name, MISSING);
         if (value != MISSING) return value;
-        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
+        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -256,10 +250,10 @@
         if (element != null) {
             if (valuesKind == VALUES) {
                 return element;                                         // Most common case.
-            } else if (element instanceof Attribute<?>) {
-                return getAttributeValue((Attribute<?>) element);
-            } else if (element instanceof FeatureAssociation) {
-                return getAssociationValue((FeatureAssociation) element);
+            } else if (element instanceof AbstractAttribute<?>) {
+                return getAttributeValue((AbstractAttribute<?>) element);
+            } else if (element instanceof AbstractAssociation) {
+                return getAssociationValue((AbstractAssociation) element);
             } else if (valuesKind == PROPERTIES) {
                 throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
             } else {
@@ -393,10 +387,10 @@
                 for (final Map.Entry<Integer,Object> entry : properties.entrySet()) {
                     final Object p = entry.getValue();
                     final Object value;
-                    if (p instanceof Attribute<?>) {
-                        value = getAttributeValue((Attribute<?>) p);
-                    } else if (p instanceof FeatureAssociation) {
-                        value = getAssociationValue((FeatureAssociation) p);
+                    if (p instanceof AbstractAttribute<?>) {
+                        value = getAttributeValue((AbstractAttribute<?>) p);
+                    } else if (p instanceof AbstractAssociation) {
+                        value = getAssociationValue((AbstractAssociation) p);
                     } else {
                         value = null;
                     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
index 21fa046..194f3df 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
@@ -38,18 +38,6 @@
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Classes;
 
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.Operation;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * An operation concatenating the string representations of the values of multiple properties.
@@ -126,7 +114,7 @@
         @Override public Class<Object>                  getTargetClass() {return Object.class;}
         @Override public Object apply(final Object f) {
             return (f != null) ? format(converter.inverse(),
-                    ((Feature) f).getPropertyValue(AttributeConvention.IDENTIFIER)) : null;
+                    ((AbstractFeature) f).getPropertyValue(AttributeConvention.IDENTIFIER)) : null;
         }
     }
 
@@ -155,7 +143,7 @@
     /**
      * The type of the result returned by the string concatenation operation.
      */
-    private final AttributeType<String> resultType;
+    private final DefaultAttributeType<String> resultType;
 
     /**
      * The characters to use at the beginning of the concatenated string, or an empty string if none.
@@ -177,11 +165,11 @@
      * It is caller's responsibility to ensure that {@code delimiter} and {@code singleAttributes} are not null.
      * This private constructor does not verify that condition on the assumption that the public API did.
      *
-     * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
+     * @see FeatureOperations#compound(Map, String, String, String, AbstractIdentifiedType...)
      */
     @SuppressWarnings({"rawtypes", "unchecked"})                                        // Generic array creation.
     StringJoinOperation(final Map<String,?> identification, final String delimiter,
-            final String prefix, final String suffix, final PropertyType[] singleAttributes)
+            final String prefix, final String suffix, final AbstractIdentifiedType[] singleAttributes)
             throws UnconvertibleObjectException
     {
         super(identification);
@@ -200,29 +188,29 @@
              * which may in turn produce an AttributeType. We do not accept more complex
              * combinations (e.g. operation producing an association).
              */
-            IdentifiedType propertyType = singleAttributes[i];
+            AbstractIdentifiedType propertyType = singleAttributes[i];
             ArgumentChecks.ensureNonNullElement("singleAttributes", i, propertyType);
             final GenericName name = propertyType.getName();
             int maximumOccurs = 0;                              // May be a bitwise combination; need only to know if > 1.
-            PropertyNotFoundException cause = null;             // In case of failure to find "sis:identifier" property.
-            final boolean isAssociation = (propertyType instanceof FeatureAssociationRole);
+            IllegalArgumentException cause = null;              // In case of failure to find "sis:identifier" property.
+            final boolean isAssociation = (propertyType instanceof DefaultAssociationRole);
             if (isAssociation) {
-                final FeatureAssociationRole role = (FeatureAssociationRole) propertyType;
-                final FeatureType ft = role.getValueType();
+                final DefaultAssociationRole role = (DefaultAssociationRole) propertyType;
+                final DefaultFeatureType ft = role.getValueType();
                 maximumOccurs = role.getMaximumOccurs();
                 try {
                     propertyType = ft.getProperty(AttributeConvention.IDENTIFIER);
-                } catch (PropertyNotFoundException e) {
+                } catch (IllegalArgumentException e) {
                     cause = e;
                 }
             }
-            if (propertyType instanceof Operation) {
-                propertyType = ((Operation) propertyType).getResult();
+            if (propertyType instanceof AbstractOperation) {
+                propertyType = ((AbstractOperation) propertyType).getResult();
             }
-            if (propertyType instanceof AttributeType) {
-                maximumOccurs |= ((AttributeType<?>) propertyType).getMaximumOccurs();
+            if (propertyType instanceof DefaultAttributeType) {
+                maximumOccurs |= ((DefaultAttributeType<?>) propertyType).getMaximumOccurs();
             } else {
-                final Class<?>[] inf = Classes.getLeafInterfaces(Classes.getClass(propertyType), PropertyType.class);
+                final Class<?>[] inf = Classes.getLeafInterfaces(Classes.getClass(propertyType), AbstractIdentifiedType.class);
                 throw new IllegalArgumentException(Resources.forProperties(identification)
                         .getString(Resources.Keys.IllegalPropertyType_2, name, (inf.length != 0) ? inf[0] : null), cause);
             }
@@ -236,7 +224,7 @@
              */
             attributeNames[i] = name.toString();
             ObjectConverter<? super String, ?> converter = ObjectConverters.find(
-                    String.class, ((AttributeType<?>) propertyType).getValueClass());
+                    String.class, ((DefaultAttributeType<?>) propertyType).getValueClass());
             if (isAssociation) {
                 converter = new ForFeature(converter);
             }
@@ -267,7 +255,7 @@
      * @return an {@code AttributeType<String>}.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return resultType;
     }
 
@@ -303,7 +291,7 @@
      * @return the concatenation of feature property values.
      */
     @Override
-    public Property apply(Feature feature, ParameterValueGroup parameters) {
+    public Property apply(AbstractFeature feature, ParameterValueGroup parameters) {
         ArgumentChecks.ensureNonNull("feature", feature);
         return new Result(feature);
     }
@@ -322,14 +310,14 @@
         private static final long serialVersionUID = -8435975199763452547L;
 
         /**
-         * The feature specified to the {@link StringJoinOperation#apply(Feature, ParameterValueGroup)} method.
+         * The feature specified to the {@link StringJoinOperation#apply(AbstractFeature, ParameterValueGroup)} method.
          */
-        private final Feature feature;
+        private final AbstractFeature feature;
 
         /**
          * Creates a new attribute for the given feature.
          */
-        Result(final Feature feature) {
+        Result(final AbstractFeature feature) {
             super(resultType);
             this.feature = feature;
         }
@@ -389,14 +377,14 @@
          * parsed, then this method does not store any property value ("all or nothing" behavior).
          *
          * @param  value  the concatenated string.
-         * @throws InvalidPropertyValueException if one of the attribute values can not be parsed to the expected type.
+         * @throws IllegalArgumentException if one of the attribute values can not be parsed to the expected type.
          */
         @Override
-        public void setValue(final String value) throws InvalidPropertyValueException {
+        public void setValue(final String value) throws IllegalArgumentException {
             final int endAt = value.length() - suffix.length();
             final boolean prefixMatches = value.startsWith(prefix);
             if (!prefixMatches || !value.endsWith(suffix)) {
-                throw new InvalidPropertyValueException(Errors.format(Errors.Keys.UnexpectedCharactersAtBound_4,
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedCharactersAtBound_4,
                         getName(),
                         prefixMatches ? 1 : 0,              // For "{1,choice,0#begin|1#end}" in message format.
                         prefixMatches ? suffix : prefix,
@@ -463,7 +451,7 @@
                     try {
                         values[count] = converter.apply(element);
                     } catch (UnconvertibleObjectException e) {
-                        throw new InvalidPropertyValueException(Errors.format(
+                        throw new IllegalArgumentException(Errors.format(
                                 Errors.Keys.CanNotAssign_2, attributeNames[count], element), e);
                     }
                 }
@@ -477,14 +465,14 @@
              * below do not fail).
              */
             if (values.length != count) {
-                throw new InvalidPropertyValueException(Resources.format(
+                throw new IllegalArgumentException(Resources.format(
                         Resources.Keys.UnexpectedNumberOfComponents_4, getName(), value, values.length, count));
             }
             for (int i=0; i < values.length; i++) {
-                Feature f   = feature;
+                AbstractFeature f = feature;
                 String name = attributeNames[i];
                 if (converters[i] instanceof ForFeature) {
-                    f = (Feature) f.getPropertyValue(name);
+                    f = (AbstractFeature) f.getPropertyValue(name);
                     name = AttributeConvention.IDENTIFIER;
                 }
                 f.setPropertyValue(name, values[i]);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java b/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
index 20697e8..bb8933e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
@@ -32,16 +32,6 @@
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.util.resources.Errors;
 
-// Branch-dependent imports
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
-
 
 /**
  * Provides validation methods to be shared by different implementations.
@@ -88,7 +78,7 @@
      * @return the {@code report}, or a new report if {@code report} was null.
      */
     private AbstractElement addViolationReport(AbstractElement report,
-            final PropertyType type, final InternationalString explanation)
+            final AbstractIdentifiedType type, final InternationalString explanation)
     {
         if (report == null) {
             final GenericName name = type.getName();
@@ -119,19 +109,19 @@
      * @param type     the type of the {@code feature} argument, provided explicitly for protecting from user overriding.
      * @param feature  the feature to validate.
      */
-    void validate(final FeatureType type, final Feature feature) {
-        for (final PropertyType pt : type.getProperties(true)) {
-            final Property property = feature.getProperty(pt.getName().toString());
+    void validate(final FeatureType type, final AbstractFeature feature) {
+        for (final AbstractIdentifiedType pt : type.getProperties(true)) {
+            final Object property = feature.getProperty(pt.getName().toString());
             final DataQuality pq;
             if (property instanceof AbstractAttribute<?>) {
                 pq = ((AbstractAttribute<?>) property).quality();
             } else if (property instanceof AbstractAssociation) {
                 pq = ((AbstractAssociation) property).quality();
-            } else if (property instanceof Attribute<?>) {
-                validate(((Attribute<?>) property).getType(), ((Attribute<?>) property).getValues());
+            } else if (property instanceof AbstractAttribute<?>) {
+                validate(((AbstractAttribute<?>) property).getType(), ((AbstractAttribute<?>) property).getValues());
                 continue;
-            } else if (property instanceof FeatureAssociation) {
-                validate(((FeatureAssociation) property).getRole(), ((FeatureAssociation) property).getValues());
+            } else if (property instanceof AbstractAssociation) {
+                validate(((AbstractAssociation) property).getRole(), ((AbstractAssociation) property).getValues());
                 continue;
             } else {
                 continue;
@@ -146,21 +136,21 @@
      * Verifies if the given value is valid for the given attribute type.
      * This method delegates to one of the {@code validate(…)} methods depending of the value type.
      */
-    void validateAny(final PropertyType type, final Object value) {
-        if (type instanceof AttributeType<?>) {
-            validate((AttributeType<?>) type, asList(value,
-                    ((AttributeType<?>) type).getMaximumOccurs()));
+    void validateAny(final AbstractIdentifiedType type, final Object value) {
+        if (type instanceof DefaultAttributeType<?>) {
+            validate((DefaultAttributeType<?>) type, asList(value,
+                    ((DefaultAttributeType<?>) type).getMaximumOccurs()));
         }
-        if (type instanceof FeatureAssociationRole) {
-            validate((FeatureAssociationRole) type, asList(value,
-                    ((FeatureAssociationRole) type).getMaximumOccurs()));
+        if (type instanceof DefaultAssociationRole) {
+            validate((DefaultAssociationRole) type, asList(value,
+                    ((DefaultAssociationRole) type).getMaximumOccurs()));
         }
     }
 
     /**
      * Verifies if the given values are valid for the given attribute type.
      */
-    void validate(final AttributeType<?> type, final Collection<?> values) {
+    void validate(final DefaultAttributeType<?> type, final Collection<?> values) {
         AbstractElement report = null;
         for (final Object value : values) {
             /*
@@ -183,10 +173,10 @@
     /**
      * Verifies if the given value is valid for the given association role.
      */
-    void validate(final FeatureAssociationRole role, final Collection<?> values) {
+    void validate(final DefaultAssociationRole role, final Collection<?> values) {
         AbstractElement report = null;
         for (final Object value : values) {
-            final FeatureType type = ((Feature) value).getType();
+            final DefaultFeatureType type = ((AbstractFeature) value).getType();
             final FeatureType valueType = role.getValueType();
             if (!valueType.isAssignableFrom(type)) {
                 report = addViolationReport(report, role, Errors.formatInternational(
@@ -204,7 +194,7 @@
      *
      * @param report  where to add the result, or {@code null} if not yet created.
      */
-    private void verifyCardinality(final AbstractElement report, final PropertyType type,
+    private void verifyCardinality(final AbstractElement report, final AbstractIdentifiedType type,
             final int minimumOccurs, final int maximumOccurs, final int count)
     {
         if (count < minimumOccurs) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java
index 92b9cca..3094b1a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java
@@ -21,22 +21,21 @@
 import org.apache.sis.feature.DefaultAssociationRole;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
  * Describes one association from the {@code FeatureType} to be built by an {@code FeatureTypeBuilder} to another
  * {@code FeatureType}. A different instance of {@code AssociationRoleBuilder} exists for each feature association
- * to describe. Those instances are created preferably by {@link FeatureTypeBuilder#addAssociation(FeatureType)},
- * or in case of cyclic reference by {@link FeatureTypeBuilder#addAssociation(GenericName)}.
+ * to describe. Those instances are created preferably by {@code FeatureTypeBuilder.addAssociation(FeatureType)},
+ * or in case of cyclic reference by {@code FeatureTypeBuilder.addAssociation(GenericName)}.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  *
  * @see org.apache.sis.feature.DefaultAssociationRole
- * @see FeatureTypeBuilder#addAssociation(FeatureType)
+ * @see FeatureTypeBuilder#addAssociation(DefaultFeatureType)
  * @see FeatureTypeBuilder#addAssociation(GenericName)
  *
  * @since 0.8
@@ -46,7 +45,7 @@
     /**
      * The target feature type, or {@code null} if unknown.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType type;
 
     /**
      * Name of the target feature type (never null).
@@ -57,7 +56,7 @@
      * The association created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient FeatureAssociationRole property;
+    private transient DefaultAssociationRole property;
 
     /**
      * Creates a new {@code AssociationRole} builder for values of the given type.
@@ -65,7 +64,7 @@
      *
      * @param owner  the builder of the {@code FeatureType} for which to add this property.
      */
-    AssociationRoleBuilder(final FeatureTypeBuilder owner, final FeatureType type, final GenericName typeName) {
+    AssociationRoleBuilder(final FeatureTypeBuilder owner, final DefaultFeatureType type, final GenericName typeName) {
         super(owner);
         this.type     = type;
         this.typeName = typeName;
@@ -76,12 +75,12 @@
      *
      * @param owner  the builder of the {@code FeatureType} for which to add this property.
      */
-    AssociationRoleBuilder(final FeatureTypeBuilder owner, final FeatureAssociationRole template) {
+    AssociationRoleBuilder(final FeatureTypeBuilder owner, final DefaultAssociationRole template) {
         super(owner);
         property      = template;
         minimumOccurs = template.getMinimumOccurs();
         maximumOccurs = template.getMaximumOccurs();
-        if (template instanceof DefaultAssociationRole && !((DefaultAssociationRole) template).isResolved()) {
+        if (!template.isResolved()) {
             type     = null;
             typeName = Features.getValueTypeName(template);
         } else {
@@ -249,10 +248,13 @@
      * If a role has already been built and this builder state has not changed since the role creation,
      * then the previously created {@code FeatureAssociationRole} instance is returned.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@code org.opengis.feature.FeatureAssociationRole} interface. This change is pending GeoAPI revision.</div>
+     *
      * @return the association role.
      */
     @Override
-    public FeatureAssociationRole build() {
+    public DefaultAssociationRole build() {
         if (property == null) {
             if (type != null) {
                 property = new DefaultAssociationRole(identification(), type, minimumOccurs, maximumOccurs);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java
index d77ca7e..3de335d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java
@@ -41,7 +41,6 @@
 import org.apache.sis.util.UnconvertibleObjectException;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
 
 
 /**
@@ -105,7 +104,7 @@
      * The attribute type created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient AttributeType<V> property;
+    private transient DefaultAttributeType<V> property;
 
     /**
      * Creates a new builder initialized to the values of the given builder.
@@ -140,16 +139,16 @@
      *
      * @param owner  the builder of the {@code FeatureType} for which to add the attribute.
      */
-    AttributeTypeBuilder(final FeatureTypeBuilder owner, final AttributeType<V> template) {
+    AttributeTypeBuilder(final FeatureTypeBuilder owner, final DefaultAttributeType<V> template) {
         super(owner);
         property      = template;
         minimumOccurs = template.getMinimumOccurs();
         maximumOccurs = template.getMaximumOccurs();
         valueClass    = template.getValueClass();
         defaultValue  = template.getDefaultValue();
-        final Map<String, AttributeType<?>> tc = template.characteristics();
+        final Map<String, DefaultAttributeType<?>> tc = template.characteristics();
         characteristics = new ArrayList<>(tc.size());
-        for (final AttributeType<?> c : tc.values()) {
+        for (final DefaultAttributeType<?> c : tc.values()) {
             characteristics.add(new CharacteristicTypeBuilder<>(this, c));
         }
         initialize(template);
@@ -519,13 +518,17 @@
      * Adds another attribute type that describes this attribute type, using an existing one as a template.
      * See <cite>"Attribute characterization"</cite> in {@link DefaultAttributeType} Javadoc for more information.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code template} argument type will be changed to {@code AttributeType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  <C>       the compile-time type of values in the {@code template} argument.
      * @param  template  an existing attribute type to use as a template.
      * @return a builder for a characteristic of this attribute, initialized with the values of the given template.
      *
      * @see #characteristics()
      */
-    public <C> CharacteristicTypeBuilder<C> addCharacteristic(final AttributeType<C> template) {
+    public <C> CharacteristicTypeBuilder<C> addCharacteristic(final DefaultAttributeType<C> template) {
         ensureNonNull("template", template);
         final CharacteristicTypeBuilder<C> characteristic = new CharacteristicTypeBuilder<>(this, template);
         characteristics.add(characteristic);
@@ -543,7 +546,7 @@
      *
      * @see #getCharacteristic(String)
      * @see #addCharacteristic(Class)
-     * @see #addCharacteristic(AttributeType)
+     * @see #addCharacteristic(DefaultAttributeType)
      * @see #setValidValues(Object...)
      * @see #setCRS(CoordinateReferenceSystem)
      */
@@ -746,12 +749,15 @@
      * By comparison, this {@code build()} method has a more accurate return type.
      * </div>
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@code org.opengis.feature.AttributeType} interface. This change is pending GeoAPI revision.</div>
+     *
      * @return the attribute type.
      */
     @Override
-    public AttributeType<V> build() {
+    public DefaultAttributeType<V> build() {
         if (property == null) {
-            final AttributeType<?>[] chrts = new AttributeType<?>[characteristics.size()];
+            final DefaultAttributeType<?>[] chrts = new DefaultAttributeType<?>[characteristics.size()];
             for (int i=0; i<chrts.length; i++) {
                 chrts[i] = characteristics.get(i).build();
             }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
index 45c316f..35d380c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
@@ -24,7 +24,6 @@
 import org.apache.sis.util.UnconvertibleObjectException;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
 
 
 /**
@@ -70,7 +69,7 @@
      * The characteristic created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient AttributeType<V> characteristic;
+    private transient DefaultAttributeType<V> characteristic;
 
     /**
      * Creates a new builder initialized to the values of the given builder but a different type.
@@ -105,7 +104,7 @@
      *
      * @param owner  the builder of the {@code AttributeType} for which to add this property.
      */
-    CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final AttributeType<V> template) {
+    CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final DefaultAttributeType<V> template) {
         super(owner.getLocale());
         this.owner     = owner;
         characteristic = template;
@@ -321,10 +320,13 @@
      * If a type has already been built and this builder state has not changed since the type creation,
      * then the previously created {@code AttributeType} instance is returned.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@code org.opengis.feature.AttributeType} interface. This change is pending GeoAPI revision.</div>
+     *
      * @return the characteristic type.
      */
     @Override
-    public AttributeType<V> build() {
+    public DefaultAttributeType<V> build() {
         if (characteristic == null) {
             characteristic = new DefaultAttributeType<>(identification(), valueClass, 0, 1, defaultValue);
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
index 3ab4bdc..41bc198 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
@@ -44,16 +44,14 @@
 import org.apache.sis.util.Numbers;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.Operation;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAssociationRole;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
- * Helper class for the creation of {@link FeatureType} instances.
+ * Helper class for the creation of {@code FeatureType} instances.
  * This builder can create the arguments to be given to the
  * {@linkplain DefaultFeatureType#DefaultFeatureType feature type constructor}
  * from simpler parameters given to this builder.
@@ -67,7 +65,7 @@
  *       and whether the feature type is {@linkplain #setAbstract abstract}.</li>
  *   <li>Convenience methods for setting the {@linkplain #setNameSpace name space} and the
  *       {@linkplain #setDefaultMultiplicity default multiplicity} of properties to be added to the feature type.</li>
- *   <li>Methods for {@linkplain #addAttribute(Class) adding an attribute}, {@linkplain #addAssociation(FeatureType)
+ *   <li>Methods for {@linkplain #addAttribute(Class) adding an attribute}, {@linkplain #addAssociation(DefaultFeatureType)
  *       an association} or {@linkplain #addProperty an operation}.</li>
  *   <li>Method for listing the previously added {@linkplain #properties() properties}.</li>
  *   <li>A {@link #build()} method for creating the {@code FeatureType} instance from all previous information.</li>
@@ -124,7 +122,7 @@
     /**
      * The parent of the feature to create. By default, new features have no parent.
      */
-    private final List<FeatureType> superTypes;
+    private final List<DefaultFeatureType> superTypes;
 
     /**
      * Whether the feature type is abstract. The default value is {@code false}.
@@ -193,7 +191,7 @@
      * The object created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient FeatureType feature;
+    private transient DefaultFeatureType feature;
 
     /**
      * Creates a new builder instance using the default name factory.
@@ -209,11 +207,13 @@
      * to values inferred from the given template. The properties list will contain properties
      * declared explicitly in the given template, not including properties inherited from super types.
      *
-     * @param template  an existing feature type to use as a template, or {@code null} if none.
+     * <div class="warning"><b>Warning:</b>
+     * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
+     * will be defined in GeoAPI.</div>
      *
-     * @see #setAll(FeatureType)
+     * @param template  an existing feature type to use as a template, or {@code null} if none.
      */
-    public FeatureTypeBuilder(final FeatureType template) {
+    public FeatureTypeBuilder(final DefaultFeatureType template) {
         this(null, null, null);
         if (template != null) {
             initialize(template);
@@ -269,14 +269,16 @@
     /**
      * Sets all properties of this builder to the values of the given feature type.
      * This builder is {@linkplain #clear() cleared} before the properties of the given type are copied.
-     * The copy is performed as documented in the {@linkplain #FeatureTypeBuilder(FeatureType) constructor}.
+     * The copy is performed as documented in the {@linkplain #FeatureTypeBuilder(DefaultFeatureType) constructor}.
+     *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
+     * will be defined in GeoAPI.</div>
      *
      * @param  template  an existing feature type to use as a template, or {@code null} if none.
      * @return {@code this} for allowing method calls chaining.
-     *
-     * @see #FeatureTypeBuilder(FeatureType)
      */
-    public FeatureTypeBuilder setAll(final FeatureType template) {
+    public FeatureTypeBuilder setAll(final DefaultFeatureType template) {
         clear();
         if (template != null) {
             initialize(template);
@@ -287,10 +289,8 @@
     /**
      * Initializes this builder to the value of the given type.
      * The caller is responsible to invoke {@link #clear()} (if needed) before this method.
-     *
-     * @see #setAll(FeatureType)
      */
-    private void initialize(final FeatureType template) {
+    private void initialize(final DefaultFeatureType template) {
         super.initialize(template);
         feature    = template;
         isAbstract = template.isAbstract();
@@ -301,12 +301,12 @@
          * is not one of the operations automatically generated by this builder.
          */
         final Map<String,Set<AttributeRole>> propertyRoles = new HashMap<>();
-        for (final PropertyType property : template.getProperties(false)) {
+        for (final AbstractIdentifiedType property : template.getProperties(false)) {
             PropertyTypeBuilder builder;
-            if (property instanceof AttributeType<?>) {
-                builder = new AttributeTypeBuilder<>(this, (AttributeType<?>) property);
-            } else if (property instanceof FeatureAssociationRole) {
-                builder = new AssociationRoleBuilder(this, (FeatureAssociationRole) property);
+            if (property instanceof DefaultAttributeType<?>) {
+                builder = new AttributeTypeBuilder<>(this, (DefaultAttributeType<?>) property);
+            } else if (property instanceof DefaultAssociationRole) {
+                builder = new AssociationRoleBuilder(this, (DefaultAssociationRole) property);
             } else {
                 builder = null;                             // Do not create OperationWrapper now - see below.
             }
@@ -404,25 +404,33 @@
     /**
      * Returns the direct parents of the feature type to create.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The return type will be changed to {@code FeatureType[]} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @return the parents of the feature type to create, or an empty array if none.
      *
      * @see DefaultFeatureType#getSuperTypes()
      */
-    public FeatureType[] getSuperTypes() {
-        return superTypes.toArray(new FeatureType[superTypes.size()]);
+    public DefaultFeatureType[] getSuperTypes() {
+        return superTypes.toArray(new DefaultFeatureType[superTypes.size()]);
     }
 
     /**
      * Sets the parent types (or super-type) from which to inherit properties.
      * If this method is not invoked, then the default value is no parent.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code parents} argument type will be changed to {@code FeatureType...} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  parents  the parent types from which to inherit properties, or an empty array if none.
      *                  Null elements are ignored.
      * @return {@code this} for allowing method calls chaining.
      */
-    public FeatureTypeBuilder setSuperTypes(final FeatureType... parents) {
+    public FeatureTypeBuilder setSuperTypes(final DefaultFeatureType... parents) {
         ensureNonNull("parents", parents);
-        final List<FeatureType> asList = Arrays.asList(parents);
+        final List<DefaultFeatureType> asList = Arrays.asList(parents);
         if (!superTypes.equals(asList)) {
             superTypes.clear();
             superTypes.addAll(asList);
@@ -595,7 +603,6 @@
      * @return {@code this} for allowing method calls chaining.
      *
      * @see AttributeRole#IDENTIFIER_COMPONENT
-     * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
      */
     public FeatureTypeBuilder setIdentifierDelimiters(final String delimiter, final String prefix, final String suffix) {
         ensureNonEmpty("delimiter", delimiter);
@@ -621,10 +628,10 @@
      *
      * @see #getProperty(String)
      * @see #addAttribute(Class)
-     * @see #addAttribute(AttributeType)
-     * @see #addAssociation(FeatureType)
+     * @see #addAttribute(DefaultAttributeType)
+     * @see #addAssociation(DefaultFeatureType)
      * @see #addAssociation(GenericName)
-     * @see #addAssociation(FeatureAssociationRole)
+     * @see #addAssociation(DefaultAssociationRole)
      */
     public List<PropertyTypeBuilder> properties() {
         return new RemoveOnlyList<>(properties);
@@ -652,8 +659,6 @@
      * @param  name   name of the property to search.
      * @return property of the given name, or {@code null} if none.
      * @throws IllegalArgumentException if the given name is ambiguous.
-     *
-     * @see #addProperty(PropertyType)
      */
     public PropertyTypeBuilder getProperty(final String name) {
         return forName(properties, name, true);
@@ -670,7 +675,7 @@
      * }
      *
      * The value class can not be {@code Feature.class} since features shall be handled
-     * as {@linkplain #addAssociation(FeatureType) associations} instead of attributes.
+     * as {@linkplain #addAssociation(DefaultFeatureType) associations} instead of attributes.
      *
      * @param  <V>         the compile-time value of {@code valueClass} argument.
      * @param  valueClass  the class of attribute values (can not be {@code Feature.class}).
@@ -680,7 +685,7 @@
      */
     public <V> AttributeTypeBuilder<V> addAttribute(final Class<V> valueClass) {
         ensureNonNull("valueClass", valueClass);
-        if (Feature.class.isAssignableFrom(valueClass)) {
+        if (AbstractFeature.class.isAssignableFrom(valueClass)) {
             // We disallow Feature.class because that type shall be handled as association instead of attribute.
             throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalArgumentValue_2, "valueClass", valueClass));
         }
@@ -695,13 +700,17 @@
      * If the new attribute duplicates an existing one (for example if the same template is used many times),
      * caller should use the returned builder for modifying some attributes.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code template} argument type will be changed to {@code AttributeType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  <V>       the compile-time type of values in the {@code template} argument.
      * @param  template  an existing attribute type to use as a template.
      * @return a builder for an {@code AttributeType}, initialized with the values of the given template.
      *
      * @see #properties()
      */
-    public <V> AttributeTypeBuilder<V> addAttribute(final AttributeType<V> template) {
+    public <V> AttributeTypeBuilder<V> addAttribute(final DefaultAttributeType<V> template) {
         ensureNonNull("template", template);
         final AttributeTypeBuilder<V> property = new AttributeTypeBuilder<>(this, template);
         properties.add(property);
@@ -764,12 +773,16 @@
      * The default association name is the name of the given type, but callers should invoke one
      * of the {@code AssociationRoleBuilder.setName(…)} methods on the returned instance with a better name.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code type} argument type will be changed to {@code FeatureType} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  type  the type of feature values.
      * @return a builder for a {@code FeatureAssociationRole}.
      *
      * @see #properties()
      */
-    public AssociationRoleBuilder addAssociation(final FeatureType type) {
+    public AssociationRoleBuilder addAssociation(final DefaultFeatureType type) {
         ensureNonNull("type", type);
         final AssociationRoleBuilder property = new AssociationRoleBuilder(this, type, type.getName());
         properties.add(property);
@@ -779,7 +792,7 @@
 
     /**
      * Creates a new {@code FeatureAssociationRole} builder for features of a type of the given name.
-     * This method can be invoked as an alternative to {@link #addAssociation(FeatureType)} when the
+     * This method can be invoked as an alternative to {@code addAssociation(FeatureType)} when the
      * {@code FeatureType} instance is not yet available because of cyclic dependency.
      *
      * @param  type  the name of the type of feature values.
@@ -801,12 +814,16 @@
      * same template is used many times), caller should use the returned builder for modifying some
      * associations.
      *
+     * <div class="warning"><b>Warning:</b>
+     * The {@code template} argument type will be changed to {@code FeatureAssociationRole} if and when such interface
+     * will be defined in GeoAPI.</div>
+     *
      * @param  template  an existing feature association to use as a template.
      * @return a builder for an {@code FeatureAssociationRole}, initialized with the values of the given template.
      *
      * @see #properties()
      */
-    public AssociationRoleBuilder addAssociation(final FeatureAssociationRole template) {
+    public AssociationRoleBuilder addAssociation(final DefaultAssociationRole template) {
         ensureNonNull("template", template);
         final AssociationRoleBuilder property = new AssociationRoleBuilder(this, template);
         properties.add(property);
@@ -819,9 +836,9 @@
      * The given property shall be an instance of one of the following types:
      *
      * <ul>
-     *   <li>{@link AttributeType}, in which case this method delegate to {@link #addAttribute(AttributeType)}.</li>
-     *   <li>{@link FeatureAssociationRole}, in which case this method delegate to {@link #addAssociation(FeatureAssociationRole)}.</li>
-     *   <li>{@link Operation}, in which case the given operation object will be added verbatim in the {@code FeatureType};
+     *   <li>{@code AttributeType}, in which case this method delegate to {@code addAttribute(AttributeType)}.</li>
+     *   <li>{@code FeatureAssociationRole}, in which case this method delegate to {@code addAssociation(FeatureAssociationRole)}.</li>
+     *   <li>{@code Operation}, in which case the given operation object will be added verbatim in the {@code FeatureType};
      *       this builder does not create new operations.</li>
      * </ul>
      *
@@ -829,6 +846,9 @@
      * If the same template is used many times, then the caller should use the returned builder
      * for modifying some properties.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the argument type may be changed to the
+     * {@code org.opengis.feature.PropertyType} interface. This change is pending GeoAPI revision.</div>
+     *
      * @param  template  the property to add to the feature type.
      * @return a builder initialized to the given template.
      *         In the {@code Operation} case, the builder is a read-only accessor on the operation properties.
@@ -836,12 +856,12 @@
      * @see #properties()
      * @see #getProperty(String)
      */
-    public PropertyTypeBuilder addProperty(final PropertyType template) {
+    public PropertyTypeBuilder addProperty(final AbstractIdentifiedType template) {
         ensureNonNull("template", template);
-        if (template instanceof AttributeType<?>) {
-            return addAttribute((AttributeType<?>) template);
-        } else if (template instanceof FeatureAssociationRole) {
-            return addAssociation((FeatureAssociationRole) template);
+        if (template instanceof DefaultAttributeType<?>) {
+            return addAttribute((DefaultAttributeType<?>) template);
+        } else if (template instanceof DefaultAssociationRole) {
+            return addAssociation((DefaultAssociationRole) template);
         } else {
             final PropertyTypeBuilder property = new OperationWrapper(this, template);
             properties.add(property);
@@ -891,6 +911,9 @@
      * One of the {@code setName(…)} methods must have been invoked before this {@code build()} method (mandatory).
      * All other methods are optional, but some calls to a {@code add} method are usually needed.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@code org.opengis.feature.FeatureType} interface. This change is pending GeoAPI revision.</div>
+     *
      * <p>If a feature type has already been built and this builder state has not changed since the
      * feature type creation, then the previously created {@code FeatureType} instance is returned.</p>
      *
@@ -900,7 +923,7 @@
      * @see #clear()
      */
     @Override
-    public FeatureType build() throws IllegalStateException {
+    public DefaultFeatureType build() throws IllegalStateException {
         if (feature == null) {
             /*
              * Creates an initial array of property types with up to 3 slots reserved for sis:identifier, sis:geometry
@@ -911,13 +934,13 @@
             int numSynthetic;                               // Number of synthetic properties that may be generated.
             int envelopeIndex = -1;
             int geometryIndex = -1;
-            final PropertyType[] identifierTypes;
+            final AbstractIdentifiedType[] identifierTypes;
             if (identifierCount == 0) {
                 numSynthetic    = 0;
                 identifierTypes = null;
             } else {
                 numSynthetic    = 1;
-                identifierTypes = new PropertyType[identifierCount];
+                identifierTypes = new AbstractIdentifiedType[identifierCount];
             }
             if (defaultGeometry != null) {
                 envelopeIndex = numSynthetic++;
@@ -925,12 +948,12 @@
                     geometryIndex = numSynthetic++;
                 }
             }
-            final PropertyType[] propertyTypes = new PropertyType[numSynthetic + numSpecified];
+            final AbstractIdentifiedType[] propertyTypes = new AbstractIdentifiedType[numSynthetic + numSpecified];
             int propertyCursor = numSynthetic;
             int identifierCursor = 0;
             for (int i=0; i<numSpecified; i++) {
                 final PropertyTypeBuilder builder = properties.get(i);
-                final PropertyType instance = builder.build();
+                final AbstractIdentifiedType instance = builder.build();
                 propertyTypes[propertyCursor] = instance;
                 /*
                  * Collect the attributes to use as identifier components while we loop over all properties.
@@ -992,7 +1015,7 @@
                 }
             }
             feature = new DefaultFeatureType(identification(), isAbstract(),
-                    superTypes.toArray(new FeatureType[superTypes.size()]),
+                    superTypes.toArray(new DefaultFeatureType[superTypes.size()]),
                     ArraysExt.resize(propertyTypes, propertyCursor));
         }
         return feature;
@@ -1036,7 +1059,7 @@
             buffer.insert(buffer.indexOf("[") + 1, "abstract ");
         }
         String separator = " : ";
-        for (final FeatureType parent : superTypes) {
+        for (final DefaultFeatureType parent : superTypes) {
             buffer.append(separator).append('“').append(parent.getName()).append('”');
             separator = ", ";
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java
index 1858944..91d5d92 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java
@@ -20,7 +20,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.PropertyType;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -37,12 +37,12 @@
     /**
      * The wrapped operation.
      */
-    private final PropertyType operation;
+    private final AbstractIdentifiedType operation;
 
     /**
      * Creates a new wrapper for the given operation.
      */
-    OperationWrapper(final FeatureTypeBuilder owner, final PropertyType operation) {
+    OperationWrapper(final FeatureTypeBuilder owner, final AbstractIdentifiedType operation) {
         super(owner);
         this.operation = operation;
         minimumOccurs = 1;
@@ -54,7 +54,7 @@
      * Returns the wrapped operation.
      */
     @Override
-    public PropertyType build() {
+    public AbstractIdentifiedType build() {
         return operation;
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java
index 40515b1..0f94ed5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java
@@ -20,10 +20,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.FeatureAssociationRole;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -33,10 +30,10 @@
  *
  * <ul>
  *   <li>{@link FeatureTypeBuilder#addAttribute(Class)}</li>
- *   <li>{@link FeatureTypeBuilder#addAttribute(AttributeType)} for using an existing attribute as a template</li>
- *   <li>{@link FeatureTypeBuilder#addAssociation(FeatureType)}</li>
+ *   <li>{@link FeatureTypeBuilder#addAttribute(DefaultAttributeType)} for using an existing attribute as a template</li>
+ *   <li>{@link FeatureTypeBuilder#addAssociation(DefaultFeatureType)}</li>
  *   <li>{@link FeatureTypeBuilder#addAssociation(GenericName)}</li>
- *   <li>{@link FeatureTypeBuilder#addAssociation(FeatureAssociationRole)} for using an existing association as a template</li>
+ *   <li>{@link FeatureTypeBuilder#addAssociation(DefaultAssociationRole)} for using an existing association as a template</li>
  * </ul>
  *
  * @author  Johann Sorel (Geomatys)
@@ -290,11 +287,14 @@
      * then the previously created {@code PropertyType} instance is returned
      * (see {@link AttributeTypeBuilder#build()} for more information).
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
+     * to {@code org.opengis.feature.PropertyType}. This change is pending GeoAPI revision.</div>
+     *
      * @return the property type.
      * @throws IllegalStateException if the builder contains inconsistent information.
      */
     @Override
-    public abstract PropertyType build() throws IllegalStateException;
+    public abstract AbstractIdentifiedType build() throws IllegalStateException;
 
     /**
      * Flags this builder as a disposed one. The builder should not be used anymore after this method call.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java
index 468997f..45c54f4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java
@@ -33,10 +33,6 @@
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.Classes;
 
-// Branch-dependent imports
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * Information common to all kind of types (feature, association, characteristics).
@@ -120,7 +116,7 @@
      * Initializes this builder to the value of the given type.
      * The caller is responsible to invoke {@link #reset()} (if needed) before this method.
      */
-    final void initialize(final IdentifiedType template) {
+    final void initialize(final AbstractIdentifiedType template) {
         putIfNonNull(AbstractIdentifiedType.NAME_KEY,        template.getName());
         putIfNonNull(AbstractIdentifiedType.DEFINITION_KEY,  template.getDefinition());
         putIfNonNull(AbstractIdentifiedType.DESIGNATION_KEY, template.getDesignation());
@@ -459,7 +455,7 @@
             }
         }
         if (ambiguity != null && nonAmbiguous) {
-            throw new PropertyNotFoundException(errors().getString(
+            throw new IllegalArgumentException(errors().getString(
                     Errors.Keys.AmbiguousName_3, best.getName(), ambiguity.getName(), name));
         }
         return best;
@@ -571,8 +567,11 @@
      * If a type has already been built and this builder state has not changed since the type creation,
      * then the previously created {@code IdentifiedType} instance is returned.
      *
+     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
+     * {@code org.opengis.feature.IdentifiedType} interface. This change is pending GeoAPI revision.</div>
+     *
      * @return the feature or property type.
      * @throws IllegalStateException if the builder contains inconsistent information.
      */
-    public abstract IdentifiedType build() throws IllegalStateException;
+    public abstract AbstractIdentifiedType build() throws IllegalStateException;
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
index cc17b1d..cb71d53 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
@@ -28,9 +28,8 @@
 import org.apache.sis.math.Fraction;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Expression;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -41,7 +40,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  *
  * @since 1.1
  * @module
@@ -70,14 +69,14 @@
      * @param  name  name of the attribute to create.
      * @return an attribute of the given name for numbers.
      */
-    static AttributeType<Number> createNumericType(final String name) {
+    static DefaultAttributeType<Number> createNumericType(final String name) {
         return createType(Number.class, name);
     }
 
     /**
      * Returns the type of results computed by this arithmetic function.
      */
-    protected abstract AttributeType<Number> expectedType();
+    protected abstract DefaultAttributeType<Number> expectedType();
 
     /**
      * Returns the type of values computed by this expression.
@@ -92,7 +91,7 @@
      * on the {@code ArithmeticFunction} subclass and is given by {@link #expectedType()}.
      */
     @Override
-    public final PropertyTypeBuilder expectedType(FeatureType ignored, FeatureTypeBuilder addTo) {
+    public final PropertyTypeBuilder expectedType(DefaultFeatureType ignored, FeatureTypeBuilder addTo) {
         return addTo.addProperty(expectedType());
     }
 
@@ -141,8 +140,8 @@
         private static final long serialVersionUID = 5445433312445869201L;
 
         /** Description of results of the {@code "Add"} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Add);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Add);
+        @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Add"} operation. */
         Add(final Expression<? super R, ? extends Number> expression1,
@@ -179,8 +178,8 @@
         private static final long serialVersionUID = 3048878022726271508L;
 
         /** Description of results of the {@code "Subtract"} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Subtract);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Subtract);
+        @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Subtract"} operation. */
         Subtract(final Expression<? super R, ? extends Number> expression1,
@@ -217,8 +216,8 @@
         private static final long serialVersionUID = -1300022614832645625L;
 
         /** Description of results of the {@code "Multiply"} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Multiply);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Multiply);
+        @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Multiply"} operation. */
         Multiply(final Expression<? super R, ? extends Number> expression1,
@@ -255,8 +254,8 @@
         private static final long serialVersionUID = -7709291845568648891L;
 
         /** Description of results of the {@code "Divide"} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Divide);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Divide);
+        @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;}
 
         /** Creates a new expression for the {@code "Divide"} operation. */
         Divide(final Expression<? super R, ? extends Number> expression1,
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/AssociationValue.java b/core/sis-feature/src/main/java/org/apache/sis/filter/AssociationValue.java
index a46b3bb..30c1a7a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/AssociationValue.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/AssociationValue.java
@@ -27,13 +27,13 @@
 import org.apache.sis.feature.builder.PropertyTypeBuilder;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
+import org.opengis.util.ScopedName;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAssociationRole;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.internal.geoapi.filter.Name;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
 
 
 /**
@@ -51,12 +51,12 @@
  * @since 1.2
  * @module
  */
-final class AssociationValue<V> extends LeafExpression<Feature, V>
-        implements ValueReference<Feature, V>, Optimization.OnExpression<Feature, V>
+final class AssociationValue<V> extends LeafExpression<AbstractFeature, V>
+        implements ValueReference<AbstractFeature, V>, Optimization.OnExpression<AbstractFeature, V>
 {
     /**
      * Path to the property from which to retrieve the value.
-     * Each element in the array is an argument to give in a call to {@link Feature#getProperty(String)}.
+     * Each element in the array is an argument to give in a call to {@code Feature.getProperty(String)}.
      * This array should be considered read-only because it may be shared.
      */
     private final String[] path;
@@ -90,6 +90,11 @@
         this.accessor = accessor;
     }
 
+    @Override
+    public final ScopedName getFunctionName() {
+        return Name.VALUE_REFERENCE;
+    }
+
     /**
      * For {@link #toString()} implementation.
      */
@@ -117,12 +122,12 @@
      * @return value for the property identified by the XPath (may be {@code null}).
      */
     @Override
-    public V apply(Feature instance) {
+    public V apply(AbstractFeature instance) {
 walk:   if (instance != null) {
             for (final String p : path) {
                 final Object value = instance.getPropertyValue(p);
-                if (!(value instanceof Feature)) break walk;
-                instance = (Feature) value;
+                if (!(value instanceof AbstractFeature)) break walk;
+                instance = (AbstractFeature) value;
             }
             return accessor.apply(instance);
         }
@@ -134,20 +139,20 @@
      * to the target properties. This is needed for better SQL WHERE clause in database queries.
      */
     @Override
-    public Expression<Feature, V> optimize(final Optimization optimization) {
-        final FeatureType specifiedType = optimization.getFeatureType();
+    public Expression<AbstractFeature, V> optimize(final Optimization optimization) {
+        final DefaultFeatureType specifiedType = optimization.getFeatureType();
 walk:   if (specifiedType != null) try {
-            FeatureType type = specifiedType;
+            DefaultFeatureType type = specifiedType;
             String[] direct = path;                 // To be cloned before any modification.
             for (int i=0; i<path.length; i++) {
-                PropertyType property = type.getProperty(path[i]);
+                AbstractIdentifiedType property = type.getProperty(path[i]);
                 Optional<String> link = Features.getLinkTarget(property);
                 if (link.isPresent()) {
                     if (direct == path) direct = direct.clone();
                     property = type.getProperty(direct[i] = link.get());
                 }
-                if (!(property instanceof FeatureAssociationRole)) break walk;
-                type = ((FeatureAssociationRole) property).getValueType();
+                if (!(property instanceof DefaultAssociationRole)) break walk;
+                type = ((DefaultAssociationRole) property).getValueType();
             }
             /*
              * At this point all links have been resolved, up to the final property to evaluate.
@@ -164,7 +169,7 @@
             if (converted != accessor || direct != path) {
                 return new AssociationValue<>(direct, converted);
             }
-        } catch (PropertyNotFoundException e) {
+        } catch (IllegalArgumentException e) {
             warning(e, true);
         }
         return this;
@@ -175,7 +180,7 @@
      */
     @Override
     @SuppressWarnings("unchecked")
-    public final <N> Expression<Feature,N> toValueType(final Class<N> target) {
+    public final <N> Expression<AbstractFeature,N> toValueType(final Class<N> target) {
         final PropertyValue<N> converted = accessor.toValueType(target);
         if (converted == accessor) {
             return (AssociationValue<N>) this;
@@ -192,22 +197,22 @@
      * @throws IllegalArgumentException if this method can not determine the property type for the given feature type.
      */
     @Override
-    public PropertyTypeBuilder expectedType(FeatureType valueType, final FeatureTypeBuilder addTo) {
+    public PropertyTypeBuilder expectedType(DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
         for (final String p : path) {
-            final PropertyType type;
+            final AbstractIdentifiedType type;
             try {
                 type = valueType.getProperty(p);
-            } catch (PropertyNotFoundException e) {
+            } catch (IllegalArgumentException e) {
                 if (accessor.isVirtual) {
                     // The association does not exist but may be defined on a yet unknown child type.
                     return accessor.expectedType(addTo);
                 }
                 throw e;
             }
-            if (!(type instanceof FeatureAssociationRole)) {
+            if (!(type instanceof DefaultAssociationRole)) {
                 return null;
             }
-            valueType = ((FeatureAssociationRole) type).getValueType();
+            valueType = ((DefaultAssociationRole) type).getValueType();
         }
         return accessor.expectedType(valueType, addTo);
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
index ad9064d..5da9907 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
@@ -27,10 +27,6 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.filter.Node;
 
-// Branch-dependent imports
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-
 
 /**
  * Base class for expressions, comparators or filters performing operations on two expressions.
@@ -42,7 +38,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>   the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>   the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <V1>  the type of value computed by the first expression.
  * @param  <V2>  the type of value computed by the second expression.
  *
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
index cd60f3f..6e8b57f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
@@ -30,15 +30,9 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.filter.Filter;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.SpatialOperator;
-import org.opengis.filter.BinarySpatialOperator;
-import org.opengis.filter.InvalidFilterValueException;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyNotFoundException;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.internal.geoapi.filter.Literal;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
 
 
 /**
@@ -51,13 +45,13 @@
  * @author  Alexis Manin (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  *
  * @since 1.1
  * @module
  */
-abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements SpatialOperator<R>, Optimization.OnFilter<R> {
+abstract class BinaryGeometryFilter<R,G> extends FilterNode<R> implements Optimization.OnFilter<R> {
     /**
      * For cross-version compatibility.
      */
@@ -65,15 +59,11 @@
 
     /**
      * The first of the two expressions to be used by this function.
-     *
-     * @see BinarySpatialOperator#getOperand1()
      */
     protected final Expression<? super R, GeometryWrapper<G>> expression1;
 
     /**
      * The second of the two expressions to be used by this function.
-     *
-     * @see BinarySpatialOperator#getOperand2()
      */
     protected final Expression<? super R, GeometryWrapper<G>> expression2;
 
@@ -134,7 +124,7 @@
                 }
             }
         } catch (FactoryException | TransformException | IncommensurableException e) {
-            throw new InvalidFilterValueException(e);
+            throw new IllegalArgumentException(e);
         }
         this.expression1 = expression1;
         this.expression2 = expression2;
@@ -151,7 +141,7 @@
     /**
      * Returns the original expression specified by the user.
      *
-     * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+     * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
      * @param  <G>  the geometry implementation type.
      * @param  expression  the expression to unwrap.
      * @return the unwrapped expression.
@@ -212,7 +202,7 @@
              * then try to fetch the CRS of the property values. If we can transform the literal to that
              * CRS, do it now in order to avoid doing this transformation for all feature instances.
              */
-            final FeatureType featureType = optimization.getFeatureType();
+            final DefaultFeatureType featureType = optimization.getFeatureType();
             if (featureType != null && other instanceof ValueReference<?,?>) try {
                 final CoordinateReferenceSystem targetCRS = AttributeConvention.getCRSCharacteristic(
                         featureType, featureType.getProperty(((ValueReference<?,?>) other).getXPath()));
@@ -220,12 +210,12 @@
                     final GeometryWrapper<G> geometry    = wrapper.apply(null);
                     final GeometryWrapper<G> transformed = geometry.transform(targetCRS);
                     if (geometry != transformed) {
-                        literal = Optimization.literal(transformed);
+                        literal = (Literal<? super R, ?>) Optimization.literal(transformed);
                         if (literal == effective1) effective1 = literal;
                         else effective2 = literal;
                     }
                 }
-            } catch (PropertyNotFoundException | TransformException e) {
+            } catch (IllegalArgumentException | TransformException e) {
                 warning(e, true);
             }
             /*
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinarySpatialFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinarySpatialFilter.java
index c7909b2..c799d72 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinarySpatialFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinarySpatialFilter.java
@@ -25,9 +25,7 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.BinarySpatialOperator;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
 
 
 /**
@@ -40,13 +38,13 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  *
  * @since 1.1
  * @module
  */
-final class BinarySpatialFilter<R,G> extends BinaryGeometryFilter<R,G> implements BinarySpatialOperator<R> {
+final class BinarySpatialFilter<R,G> extends BinaryGeometryFilter<R,G> {
     /**
      * For cross-version compatibility.
      */
@@ -115,7 +113,6 @@
     /**
      * Returns the first expression to be evaluated.
      */
-    @Override
     public Expression<? super R, ?> getOperand1() {
         return original(expression1);
     }
@@ -123,7 +120,6 @@
     /**
      * Returns the second expression to be evaluated.
      */
-    @Override
     public Expression<? super R, ?> getOperand2() {
         return original(expression2);
     }
@@ -139,7 +135,7 @@
     /**
      * Given an object, determines if the test(s) represented by this filter are passed.
      *
-     * @param  object  the object (often a {@link Feature} instance) to evaluate.
+     * @param  object  the object (often a {@code Feature} instance) to evaluate.
      * @return {@code true} if the test(s) are passed for the provided object.
      */
     @Override
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java
deleted file mode 100644
index e645968..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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.filter;
-
-import java.util.Set;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Optional;
-import org.opengis.util.LocalName;
-import org.apache.sis.util.collection.CodeListSet;
-import org.apache.sis.internal.feature.AttributeConvention;
-
-// Branch-dependent imports
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.capability.Conformance;
-import org.opengis.filter.capability.IdCapabilities;
-import org.opengis.filter.capability.FilterCapabilities;
-import org.opengis.filter.capability.ScalarCapabilities;
-import org.opengis.filter.capability.SpatialCapabilities;
-import org.opengis.filter.capability.TemporalCapabilities;
-
-
-/**
- * Metadata about the specific elements that Apache SIS implementation supports.
- *
- * @todo Missing {@link SpatialCapabilities} and {@link TemporalCapabilities}.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-final class Capabilities implements FilterCapabilities, Conformance, IdCapabilities, ScalarCapabilities {
-    /**
-     * The unique instance of the capabilities document.
-     */
-    static final Capabilities INSTANCE = new Capabilities();
-
-    /**
-     * Creates a new capability document.
-     */
-    private Capabilities() {
-    }
-
-    /**
-     * Declaration of which conformance classes a particular implementation supports.
-     */
-    @Override
-    public Conformance getConformance() {
-        return this;
-    }
-
-    /**
-     * Returns whether the implementation supports the <cite>Resource Identification</cite> conformance level.
-     */
-    @Override
-    public boolean implementsResourceld() {
-        return true;
-    }
-
-    /**
-     * Provides capabilities used to convey supported identifier operators.
-     */
-    @Override
-    public Optional<IdCapabilities> getIdCapabilities() {
-        return Optional.of(this);
-    }
-
-    /**
-     * Declares the names of the properties used for resource identifiers.
-     */
-    @Override
-    public Collection<LocalName> getResourceIdentifiers() {
-        return Collections.singleton(AttributeConvention.IDENTIFIER_PROPERTY.tip());
-    }
-
-    /**
-     * Returns whether the implementation supports the <cite>Standard Filter</cite> conformance level.
-     * A value of {@code true} means that all the logical operators ({@code And}, {@code Or}, {@code Not})
-     * are supported, together with all the standard {@link ComparisonOperatorName}. Those operators shall
-     * be listed in the {@linkplain FilterCapabilities#getScalarCapabilities() scalar capabilities}.
-     */
-    @Override
-    public boolean implementsStandardFilter() {
-        return true;
-    }
-
-    /**
-     * Indicates that SIS supports <cite>And</cite>, <cite>Or</cite> and <cite>Not</cite> operators.
-     */
-    @Override
-    public boolean hasLogicalOperators() {
-        return true;
-    }
-
-    /**
-     * Advertises which logical, comparison and arithmetic operators the service supports.
-     */
-    @Override
-    public Optional<ScalarCapabilities> getScalarCapabilities() {
-        return Optional.of(this);
-    }
-
-    /**
-     * Advertises that SIS supports all comparison operators.
-     */
-    @Override
-    public Set<ComparisonOperatorName> getComparisonOperators() {
-        return new CodeListSet<>(ComparisonOperatorName.class, true);
-    }
-
-    /**
-     * Indicates that Apache SIS supports the <cite>Spatial Filter</cite> conformance level.
-     *
-     * @todo Need to implement {@linkplain FilterCapabilities#getSpatialCapabilities() temporal capabilities}.
-     */
-    @Override
-    public boolean implementsSpatialFilter() {
-        return true;
-    }
-
-    /**
-     * Indicates that Apache SIS supports the <cite>Temporal Filter</cite> conformance level.
-     *
-     * @todo Need to implement {@linkplain FilterCapabilities#getTemporalCapabilities() temporal capabilities}.
-     */
-    @Override
-    public boolean implementsTemporalFilter() {
-        return true;
-    }
-
-    /**
-     * Indicates that Apache SIS supports the <cite>Sorting</cite> conformance level.
-     */
-    @Override
-    public boolean implementsSorting() {
-        return true;
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java
index 9ac3165..969d9a5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFilter.java
@@ -39,12 +39,10 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.MatchAction;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.BetweenComparisonOperator;
+import org.apache.sis.internal.geoapi.filter.MatchAction;
+import org.apache.sis.internal.geoapi.filter.ComparisonOperatorName;
+import org.apache.sis.internal.geoapi.filter.BinaryComparisonOperator;
+import org.apache.sis.internal.geoapi.filter.BetweenComparisonOperator;
 
 
 /**
@@ -70,7 +68,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  *
  * @since 1.1
  * @module
@@ -801,6 +799,10 @@
             this.upper = new    LessThanOrEqualTo<>(expression, upper, true, MatchAction.ANY);
         }
 
+        @Override public ComparisonOperatorName getOperatorType() {
+            return ComparisonOperatorName.PROPERTY_IS_BETWEEN;
+        }
+
         /**
          * Returns the 3 children of this node. Since {@code lower.expression2}
          * is the same as {@code upper.expression1}, that repetition is omitted.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
index 90adf3a..948f813 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
@@ -29,8 +29,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -39,7 +38,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.2
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <S>  the type of value computed by the wrapped exception. This is the type to convert.
  * @param  <V>  the type of value computed by this expression. This is the type after conversion.
  *
@@ -161,7 +160,7 @@
      * May return {@code null} if the type can not be determined.
      */
     @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
+    public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
         final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(expression);
         if (fex == null) {
             return null;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
index f213150..b8e5678 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
@@ -20,7 +20,6 @@
 import java.util.HashMap;
 import java.util.Collection;
 import java.util.ServiceLoader;
-import java.time.Instant;
 import javax.measure.Quantity;
 import javax.measure.quantity.Length;
 import org.opengis.geometry.Envelope;
@@ -36,21 +35,29 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.filter.*;
-import org.opengis.feature.Feature;
-import org.opengis.filter.capability.FilterCapabilities;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.internal.geoapi.filter.MatchAction;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.internal.geoapi.filter.DistanceOperatorName;
 
 
 /**
  * A factory of default {@link Filter} and {@link Expression} implementations.
  * This base class operates on resources of arbitrary type {@code <R>}.
- * Concrete subclass operates on resources of specific type such as {@link org.opengis.feature.Feature}.
+ * Concrete subclass operates on resources of specific type such as {@link AbstractFeature}.
+ *
+ * <div class="warning"><b>Upcoming API change</b><br>
+ * In a future version, all {@link Filter} and {@link Expression} parameters may be replaced by parameters
+ * of the same names but from the {@code org.opengis.filter} package instead of {@code org.apache.sis.filter}.
+ * This change is pending next GeoAPI release.
+ * In addition, return types may become more specialized types.
+ * </div>
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.2
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) to use as inputs.
+ * @param  <R>  the type of resources (e.g. {@link AbstractFeature}) to use as inputs.
  * @param  <G>  base class of geometry objects. The implementation-neutral type is GeoAPI {@link Geometry},
  *              but this factory allows the use of other implementations such as JTS
  *              {@link org.locationtech.jts.geom.Geometry} or ESRI {@link com.esri.core.geometry.Geometry}.
@@ -59,7 +66,7 @@
  * @since 1.1
  * @module
  */
-public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implements FilterFactory<R,G,T> {
+public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory {
     /**
      * The geometry library used by this factory.
      */
@@ -123,48 +130,36 @@
     }
 
     /**
-     * Returns a factory operating on {@link Feature} instances.
+     * Returns a factory operating on {@link AbstractFeature} instances.
      * The {@linkplain GeometryLibrary geometry library} will be the system default.
      *
-     * @return factory operating on {@link Feature} instances.
+     * @return factory operating on {@link AbstractFeature} instances.
      *
      * @todo The type of temporal object is not yet determined.
      */
-    public static FilterFactory<Feature, Object, Object> forFeatures() {
+    public static DefaultFilterFactory<AbstractFeature, Object, Object> forFeatures() {
         return Features.DEFAULT;
     }
 
     /**
-     * Describes the abilities of this factory. The description includes restrictions on
-     * the available spatial operations, scalar operations, lists of supported functions,
-     * and description of which geometry literals are understood.
-     *
-     * @return description of the abilities of this factory.
-     */
-    @Override
-    public FilterCapabilities getCapabilities() {
-        return Capabilities.INSTANCE;
-    }
-
-    /**
-     * A filter factory operating on {@link Feature} instances.
+     * A filter factory operating on {@link AbstractFeature} instances.
      *
      * @param  <G>  base class of geometry objects. The implementation-neutral type is GeoAPI {@link Geometry},
      *              but this factory allows the use of other implementations such as JTS
      *              {@link org.locationtech.jts.geom.Geometry} or ESRI {@link com.esri.core.geometry.Geometry}.
      * @param  <T>  base class of temporal objects.
      */
-    public static class Features<G,T> extends DefaultFilterFactory<Feature, G, T> {
+    public static class Features<G,T> extends DefaultFilterFactory<AbstractFeature, G, T> {
         /**
          * The instance using system default.
          *
          * @see #forFeatures()
          */
-        static final FilterFactory<Feature,Object,Object> DEFAULT =
+        static final DefaultFilterFactory<AbstractFeature,Object,Object> DEFAULT =
                 new Features<>(Object.class, Object.class, WraparoundMethod.SPLIT);;
 
         /**
-         * Creates a new factory operating on {@link Feature} instances.
+         * Creates a new factory operating on {@link AbstractFeature} instances.
          * See the {@linkplain DefaultFilterFactory#DefaultFilterFactory(Class, Class, WraparoundMethod)}
          * super-class constructor} for a list of valid class arguments.
          *
@@ -180,34 +175,12 @@
 
         /**
          * Creates a new predicate to identify an identifiable resource within a filter expression.
-         * The predicate uses no versioning and no time range.
          *
          * @param  identifier  identifier of the resource that shall be selected by the predicate.
          * @return the predicate.
          */
         @Override
-        public ResourceId<Feature> resourceId(final String identifier) {
-            return new IdentifierFilter<>(identifier);
-        }
-
-        /**
-         * Creates a new predicate to identify an identifiable resource within a filter expression.
-         * If {@code startTime} and {@code endTime} are non-null, the filter will select all versions
-         * of a resource between the specified dates.
-         *
-         * @param  identifier  identifier of the resource that shall be selected by the predicate.
-         * @param  version     version of the resource to select, or {@code null} for any version.
-         * @param  startTime   start time of the resource to select, or {@code null} if none.
-         * @param  endTime     end time of the resource to select, or {@code null} if none.
-         * @return the predicate.
-         *
-         * @todo Current implementation ignores the version, start time and end time.
-         *       This limitation may be resolved in a future version.
-         */
-        @Override
-        public ResourceId<Feature> resourceId(final String identifier, final Version version,
-                                              final Instant startTime, final Instant endTime)
-        {
+        public Filter<AbstractFeature> resourceId(final String identifier) {
             return new IdentifierFilter<>(identifier);
         }
 
@@ -226,7 +199,7 @@
          * @return an expression evaluating the referenced property value.
          */
         @Override
-        public <V> ValueReference<Feature,V> property(final String xpath, final Class<V> type) {
+        public <V> Expression<AbstractFeature,V> property(final String xpath, final Class<V> type) {
             ArgumentChecks.ensureNonEmpty("xpath", xpath);
             ArgumentChecks.ensureNonNull ("type",  type);
             return PropertyValue.create(xpath, type);
@@ -234,6 +207,41 @@
     }
 
     /**
+     * Creates a predicate to identify an identifiable resource within a filter expression.
+     *
+     * @param  rid  identifier of the resource that shall be selected by the predicate.
+     * @return the predicate.
+     */
+    public abstract Filter<R> resourceId(String rid);
+
+    /**
+     * Creates an expression whose value is computed by retrieving the value indicated by a path in a resource.
+     * If all characters in the path are {@linkplain Character#isUnicodeIdentifierPart(int) Unicode identifier parts},
+     * then the XPath expression is simply a property name.
+     *
+     * @param  xpath  the path to the property whose value will be returned by the {@code apply(R)} method.
+     * @return an expression evaluating the referenced property value.
+     */
+    public Expression<R,?> property(String xpath) {
+        return property(xpath, Object.class);
+    }
+
+    /**
+     * Creates an expression retrieving the value as an instance of the specified class.
+     * The {@code xpath} argument follows the rule described in {@link #property(String)}.
+     *
+     * <p>The desired type of property values can be specified. For example if the property values should be numbers,
+     * then {@code type} can be <code>{@linkplain Number}.class</code>. If property values can be of any type with no
+     * conversion desired, then {@code type} should be {@code Object.class}.</p>
+     *
+     * @param  <V>    the type of the values to be fetched (compile-time value of {@code type}).
+     * @param  xpath  the path to the property whose value will be returned by the {@code apply(R)} method.
+     * @param  type   the type of the values to be fetched (run-time value of {@code <V>}).
+     * @return an expression evaluating the referenced property value.
+     */
+    public abstract <V> Expression<R,V> property(String xpath, Class<V> type);
+
+    /**
      * Creates a constant, literal value that can be used in expressions.
      * The given value should be data objects such as strings, numbers, dates or geometries.
      *
@@ -241,8 +249,7 @@
      * @param  value  the literal value. May be {@code null}.
      * @return a literal for the given value.
      */
-    @Override
-    public <V> Literal<R,V> literal(final V value) {
+    public <V> Expression<R,V> literal(final V value) {
         return new LeafExpression.Literal<>(value);
     }
 
@@ -251,19 +258,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} = {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_EQUAL_TO
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> equal(final Expression<? super R, ?> expression1,
-                                             final Expression<? super R, ?> expression2,
-                                             boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> equal(final Expression<? super R, ?> expression1,
+                           final Expression<? super R, ?> expression2)
     {
-        return new ComparisonFilter.EqualTo<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.EqualTo<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -271,19 +271,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} ≠ {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_NOT_EQUAL_TO
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> notEqual(final Expression<? super R, ?> expression1,
-                                                final Expression<? super R, ?> expression2,
-                                                boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> notEqual(final Expression<? super R, ?> expression1,
+                              final Expression<? super R, ?> expression2)
     {
-        return new ComparisonFilter.NotEqualTo<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.NotEqualTo<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -291,19 +284,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} &lt; {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_LESS_THAN
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> less(final Expression<? super R, ?> expression1,
-                                            final Expression<? super R, ?> expression2,
-                                            boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> less(final Expression<? super R, ?> expression1,
+                          final Expression<? super R, ?> expression2)
     {
-        return new ComparisonFilter.LessThan<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.LessThan<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -311,19 +297,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} &gt; {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_GREATER_THAN
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> greater(final Expression<? super R, ?> expression1,
-                                               final Expression<? super R, ?> expression2,
-                                               boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> greater(final Expression<? super R, ?> expression1,
+                             final Expression<? super R, ?> expression2)
     {
-        return new ComparisonFilter.GreaterThan<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.GreaterThan<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -331,19 +310,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} ≤ {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_LESS_THAN_OR_EQUAL_TO
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> lessOrEqual(final Expression<? super R, ?> expression1,
-                                                   final Expression<? super R, ?> expression2,
-                                                   boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> lessOrEqual(final Expression<? super R, ?> expression1,
+                                 final Expression<? super R, ?> expression2)
     {
-        return new ComparisonFilter.LessThanOrEqualTo<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.LessThanOrEqualTo<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -351,19 +323,12 @@
      *
      * @param  expression1     the first of the two expressions to be used by this comparator.
      * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
      * @return a filter evaluating {@code expression1} ≥ {@code expression2}.
-     *
-     * @see ComparisonOperatorName#PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO
-     * @todo Revisit if we can be more specific on the second parameterized type in expressions.
      */
-    @Override
-    public BinaryComparisonOperator<R> greaterOrEqual(final Expression<? super R, ?> expression1,
-                                                      final Expression<? super R, ?> expression2,
-                                                      boolean isMatchingCase, MatchAction matchAction)
+    public Filter<R> greaterOrEqual(final Expression<? super R, ?> expression1,
+                                    final Expression<? super R, ?> expression2)
     {
-        return new ComparisonFilter.GreaterThanOrEqualTo<>(expression1, expression2, isMatchingCase, matchAction);
+        return new ComparisonFilter.GreaterThanOrEqualTo<>(expression1, expression2, true, MatchAction.ANY);
     }
 
     /**
@@ -376,15 +341,27 @@
      * @return a filter evaluating ({@code expression} ≥ {@code lowerBoundary})
      *                       &amp; ({@code expression} ≤ {@code upperBoundary}).
      */
-    @Override
-    public BetweenComparisonOperator<R> between(final Expression<? super R, ?> expression,
-                                                final Expression<? super R, ?> lowerBoundary,
-                                                final Expression<? super R, ?> upperBoundary)
+    public Filter<R> between(final Expression<? super R, ?> expression,
+                             final Expression<? super R, ?> lowerBoundary,
+                             final Expression<? super R, ?> upperBoundary)
     {
         return new ComparisonFilter.Between<>(expression, lowerBoundary, upperBoundary);
     }
 
     /**
+     * Character string comparison operator with pattern matching and default wildcards.
+     * The wildcard character is {@code '%'}, the single character is {@code '_'} and
+     * the escape character is {@code '\\'}. The comparison is case-sensitive.
+     *
+     * @param  expression  source of values to compare against the pattern.
+     * @param  pattern     pattern to match against expression values.
+     * @return a character string comparison operator with pattern matching.
+     */
+    public Filter<R> like(Expression<? super R, ?> expression, String pattern) {
+        return like(expression, pattern, '%', '_', '\\', true);
+    }
+
+    /**
      * Character string comparison operator with pattern matching and specified wildcards.
      *
      * @param  expression      source of values to compare against the pattern.
@@ -395,8 +372,7 @@
      * @param  isMatchingCase  specifies how a filter expression processor should perform string comparisons.
      * @return a character string comparison operator with pattern matching.
      */
-    @Override
-    public LikeOperator<R> like(final Expression<? super R, ?> expression, final String pattern,
+    public Filter<R> like(final Expression<? super R, ?> expression, final String pattern,
             final char wildcard, final char singleChar, final char escape, final boolean isMatchingCase)
     {
         return new LikeFilter<>(expression, pattern, wildcard, singleChar, escape, isMatchingCase);
@@ -409,14 +385,13 @@
      * @param  expression  source of values to compare against {@code null}.
      * @return a filter that checks if an expression's value is {@code null}.
      */
-    @Override
-    public NullOperator<R> isNull(final Expression<? super R, ?> expression) {
+    public Filter<R> isNull(final Expression<? super R, ?> expression) {
         return new UnaryFunction.IsNull<>(expression);
     }
 
     /**
      * An operator that tests if an expression's value is nil.
-     * The difference with {@link NullOperator} is that a value should exist
+     * The difference with {@code NullOperator} is that a value should exist
      * but can not be provided for the reason given by {@code nilReason}.
      * Possible reasons are:
      *
@@ -438,8 +413,7 @@
      * @see org.apache.sis.xml.NilObject
      * @see org.apache.sis.xml.NilReason
      */
-    @Override
-    public NilOperator<R> isNil(final Expression<? super R, ?> expression, final String nilReason) {
+    public Filter<R> isNil(final Expression<? super R, ?> expression, final String nilReason) {
         return new UnaryFunction.IsNil<>(expression, nilReason);
     }
 
@@ -449,11 +423,8 @@
      * @param  operand1  the first operand of the AND operation.
      * @param  operand2  the second operand of the AND operation.
      * @return a filter evaluating {@code operand1 AND operand2}.
-     *
-     * @see LogicalOperatorName#AND
      */
-    @Override
-    public LogicalOperator<R> and(final Filter<? super R> operand1, final Filter<? super R> operand2) {
+    public Filter<R> and(final Filter<? super R> operand1, final Filter<? super R> operand2) {
         ArgumentChecks.ensureNonNull("operand1", operand1);
         ArgumentChecks.ensureNonNull("operand2", operand2);
         return new LogicalFilter.And<>(operand1, operand2);
@@ -465,11 +436,8 @@
      * @param  operands  a collection of at least 2 operands.
      * @return a filter evaluating {@code operand1 AND operand2 AND operand3}…
      * @throws IllegalArgumentException if the given collection contains less than 2 elements.
-     *
-     * @see LogicalOperatorName#AND
      */
-    @Override
-    public LogicalOperator<R> and(final Collection<? extends Filter<? super R>> operands) {
+    public Filter<R> and(final Collection<? extends Filter<? super R>> operands) {
         return new LogicalFilter.And<>(operands);
     }
 
@@ -479,11 +447,8 @@
      * @param  operand1  the first operand of the OR operation.
      * @param  operand2  the second operand of the OR operation.
      * @return a filter evaluating {@code operand1 OR operand2}.
-     *
-     * @see LogicalOperatorName#OR
      */
-    @Override
-    public LogicalOperator<R> or(final Filter<? super R> operand1, final Filter<? super R> operand2) {
+    public Filter<R> or(final Filter<? super R> operand1, final Filter<? super R> operand2) {
         ArgumentChecks.ensureNonNull("operand1", operand1);
         ArgumentChecks.ensureNonNull("operand2", operand2);
         return new LogicalFilter.Or<>(operand1, operand2);
@@ -495,11 +460,8 @@
      * @param  operands  a collection of at least 2 operands.
      * @return a filter evaluating {@code operand1 OR operand2 OR operand3}…
      * @throws IllegalArgumentException if the given collection contains less than 2 elements.
-     *
-     * @see LogicalOperatorName#OR
      */
-    @Override
-    public LogicalOperator<R> or(final Collection<? extends Filter<? super R>> operands) {
+    public Filter<R> or(final Collection<? extends Filter<? super R>> operands) {
         return new LogicalFilter.Or<>(operands);
     }
 
@@ -508,11 +470,8 @@
      *
      * @param  operand  the operand of the NOT operation.
      * @return a filter evaluating {@code NOT operand}.
-     *
-     * @see LogicalOperatorName#NOT
      */
-    @Override
-    public LogicalOperator<R> not(final Filter<? super R> operand) {
+    public Filter<R> not(final Filter<? super R> operand) {
         return new LogicalFilter.Not<>(operand);
     }
 
@@ -523,13 +482,8 @@
      * @param  geometry  expression fetching the geometry to check for interaction with bounds.
      * @param  bounds    the bounds to check geometry against.
      * @return a filter checking for any interactions between the bounding boxes.
-     *
-     * @see SpatialOperatorName#BBOX
-     *
-     * @todo Maybe the expression parameterized type should extend {@link Geometry}.
      */
-    @Override
-    public BinarySpatialOperator<R> bbox(final Expression<? super R, ? extends G> geometry, final Envelope bounds) {
+    public Filter<R> bbox(final Expression<? super R, ? extends G> geometry, final Envelope bounds) {
         return new BinarySpatialFilter<>(library, geometry, bounds, wraparound);
     }
 
@@ -539,12 +493,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Equals" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#EQUALS
      */
-    @Override
-    public BinarySpatialOperator<R> equals(final Expression<? super R, ? extends G> geometry1,
-                                           final Expression<? super R, ? extends G> geometry2)
+    public Filter<R> equals(final Expression<? super R, ? extends G> geometry1,
+                            final Expression<? super R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.EQUALS, library, geometry1, geometry2);
     }
@@ -555,12 +506,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Disjoint" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#DISJOINT
      */
-    @Override
-    public BinarySpatialOperator<R> disjoint(final Expression<? super R, ? extends G> geometry1,
-                                             final Expression<? super R, ? extends G> geometry2)
+    public Filter<R> disjoint(final Expression<? super R, ? extends G> geometry1,
+                              final Expression<? super R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.DISJOINT, library, geometry1, geometry2);
     }
@@ -571,12 +519,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Intersects" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#INTERSECTS
      */
-    @Override
-    public BinarySpatialOperator<R> intersects(final Expression<? super R, ? extends G> geometry1,
-                                               final Expression<? super R, ? extends G> geometry2)
+    public Filter<R> intersects(final Expression<? super R, ? extends G> geometry1,
+                                final Expression<? super R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.INTERSECTS, library, geometry1, geometry2);
     }
@@ -587,12 +532,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Touches" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#TOUCHES
      */
-    @Override
-    public BinarySpatialOperator<R> touches(final Expression<? super R, ? extends G> geometry1,
-                                            final Expression<? super R, ? extends G> geometry2)
+    public Filter<R> touches(final Expression<? super R, ? extends G> geometry1,
+                             final Expression<? super R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.TOUCHES, library, geometry1, geometry2);
     }
@@ -603,12 +545,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Crosses" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#CROSSES
      */
-    @Override
-    public BinarySpatialOperator<R> crosses(final Expression<? super R, ? extends G> geometry1,
-                                            final Expression<? super R, ? extends G> geometry2)
+    public Filter<R> crosses(final Expression<? super R, ? extends G> geometry1,
+                             final Expression<? super R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.CROSSES, library, geometry1, geometry2);
     }
@@ -620,12 +559,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Within" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#WITHIN
      */
-    @Override
-    public BinarySpatialOperator<R> within(final Expression<? super R, ? extends G> geometry1,
-                                           final Expression<? super R, ? extends G> geometry2)
+    public Filter<R> within(final Expression<? super R, ? extends G> geometry1,
+                            final Expression<? super R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.WITHIN, library, geometry1, geometry2);
     }
@@ -636,12 +572,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Contains" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#CONTAINS
      */
-    @Override
-    public BinarySpatialOperator<R> contains(final Expression<? super R, ? extends G> geometry1,
-                                             final Expression<? super R, ? extends G> geometry2)
+    public Filter<R> contains(final Expression<? super R, ? extends G> geometry1,
+                              final Expression<? super R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.CONTAINS, library, geometry1, geometry2);
     }
@@ -653,12 +586,9 @@
      * @param  geometry1  expression fetching the first geometry of the binary operator.
      * @param  geometry2  expression fetching the second geometry of the binary operator.
      * @return a filter for the "Overlaps" operation between the two geometries.
-     *
-     * @see SpatialOperatorName#OVERLAPS
      */
-    @Override
-    public BinarySpatialOperator<R> overlaps(final Expression<? super R, ? extends G> geometry1,
-                                             final Expression<? super R, ? extends G> geometry2)
+    public Filter<R> overlaps(final Expression<? super R, ? extends G> geometry1,
+                              final Expression<? super R, ? extends G> geometry2)
     {
         return new BinarySpatialFilter<>(SpatialOperatorName.OVERLAPS, library, geometry1, geometry2);
     }
@@ -672,13 +602,10 @@
      * @param  distance   minimal distance for evaluating the expression as {@code true}.
      * @return operator that evaluates to {@code true} when all of a feature's geometry
      *         is more distant than the given distance from the second geometry.
-     *
-     * @see DistanceOperatorName#BEYOND
      */
-    @Override
-    public DistanceOperator<R> beyond(final Expression<? super R, ? extends G> geometry1,
-                                      final Expression<? super R, ? extends G> geometry2,
-                                      final Quantity<Length> distance)
+    public Filter<R> beyond(final Expression<? super R, ? extends G> geometry1,
+                            final Expression<? super R, ? extends G> geometry2,
+                            final Quantity<Length> distance)
     {
         return new DistanceFilter<>(DistanceOperatorName.BEYOND, library, geometry1, geometry2, distance);
     }
@@ -692,13 +619,10 @@
      * @param  distance   maximal distance for evaluating the expression as {@code true}.
      * @return operator that evaluates to {@code true} when any part of the feature's geometry
      *         lies within the given distance of the second geometry.
-     *
-     * @see DistanceOperatorName#WITHIN
      */
-    @Override
-    public DistanceOperator<R> within(final Expression<? super R, ? extends G> geometry1,
-                                      final Expression<? super R, ? extends G> geometry2,
-                                      final Quantity<Length> distance)
+    public Filter<R> within(final Expression<? super R, ? extends G> geometry1,
+                            final Expression<? super R, ? extends G> geometry2,
+                            final Quantity<Length> distance)
     {
         return new DistanceFilter<>(DistanceOperatorName.WITHIN, library, geometry1, geometry2, distance);
     }
@@ -709,12 +633,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "After" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#AFTER
      */
-    @Override
-    public TemporalOperator<R> after(final Expression<? super R, ? extends T> time1,
-                                     final Expression<? super R, ? extends T> time2)
+    public Filter<R> after(final Expression<? super R, ? extends T> time1,
+                           final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.After<>(time1, time2);
     }
@@ -725,12 +646,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "Before" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#BEFORE
      */
-    @Override
-    public TemporalOperator<R> before(final Expression<? super R, ? extends T> time1,
-                                      final Expression<? super R, ? extends T> time2)
+    public Filter<R> before(final Expression<? super R, ? extends T> time1,
+                            final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.Before<>(time1, time2);
     }
@@ -741,12 +659,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "Begins" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#BEGINS
      */
-    @Override
-    public TemporalOperator<R> begins(final Expression<? super R, ? extends T> time1,
-                                      final Expression<? super R, ? extends T> time2)
+    public Filter<R> begins(final Expression<? super R, ? extends T> time1,
+                            final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.Begins<>(time1, time2);
     }
@@ -757,12 +672,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "BegunBy" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#BEGUN_BY
      */
-    @Override
-    public TemporalOperator<R> begunBy(final Expression<? super R, ? extends T> time1,
-                                       final Expression<? super R, ? extends T> time2)
+    public Filter<R> begunBy(final Expression<? super R, ? extends T> time1,
+                             final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.BegunBy<>(time1, time2);
     }
@@ -773,12 +685,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "TContains" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#CONTAINS
      */
-    @Override
-    public TemporalOperator<R> tcontains(final Expression<? super R, ? extends T> time1,
-                                         final Expression<? super R, ? extends T> time2)
+    public Filter<R> tcontains(final Expression<? super R, ? extends T> time1,
+                               final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.Contains<>(time1, time2);
     }
@@ -789,12 +698,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "During" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#DURING
      */
-    @Override
-    public TemporalOperator<R> during(final Expression<? super R, ? extends T> time1,
-                                      final Expression<? super R, ? extends T> time2)
+    public Filter<R> during(final Expression<? super R, ? extends T> time1,
+                            final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.During<>(time1, time2);
     }
@@ -805,12 +711,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "TEquals" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#EQUALS
      */
-    @Override
-    public TemporalOperator<R> tequals(final Expression<? super R, ? extends T> time1,
-                                       final Expression<? super R, ? extends T> time2)
+    public Filter<R> tequals(final Expression<? super R, ? extends T> time1,
+                             final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.Equals<>(time1, time2);
     }
@@ -821,12 +724,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "TOverlaps" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#OVERLAPS
      */
-    @Override
-    public TemporalOperator<R> toverlaps(final Expression<? super R, ? extends T> time1,
-                                         final Expression<? super R, ? extends T> time2)
+    public Filter<R> toverlaps(final Expression<? super R, ? extends T> time1,
+                               final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.Overlaps<>(time1, time2);
     }
@@ -837,12 +737,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "Meets" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#MEETS
      */
-    @Override
-    public TemporalOperator<R> meets(final Expression<? super R, ? extends T> time1,
-                                     final Expression<? super R, ? extends T> time2)
+    public Filter<R> meets(final Expression<? super R, ? extends T> time1,
+                           final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.Meets<>(time1, time2);
     }
@@ -853,12 +750,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "Ends" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#ENDS
      */
-    @Override
-    public TemporalOperator<R> ends(final Expression<? super R, ? extends T> time1,
-                                    final Expression<? super R, ? extends T> time2)
+    public Filter<R> ends(final Expression<? super R, ? extends T> time1,
+                          final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.Ends<>(time1, time2);
     }
@@ -869,12 +763,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "OverlappedBy" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#OVERLAPPED_BY
      */
-    @Override
-    public TemporalOperator<R> overlappedBy(final Expression<? super R, ? extends T> time1,
-                                            final Expression<? super R, ? extends T> time2)
+    public Filter<R> overlappedBy(final Expression<? super R, ? extends T> time1,
+                                  final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.OverlappedBy<>(time1, time2);
     }
@@ -885,12 +776,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "MetBy" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#MET_BY
      */
-    @Override
-    public TemporalOperator<R> metBy(final Expression<? super R, ? extends T> time1,
-                                     final Expression<? super R, ? extends T> time2)
+    public Filter<R> metBy(final Expression<? super R, ? extends T> time1,
+                           final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.MetBy<>(time1, time2);
     }
@@ -901,12 +789,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "EndedBy" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#ENDED_BY
      */
-    @Override
-    public TemporalOperator<R> endedBy(final Expression<? super R, ? extends T> time1,
-                                       final Expression<? super R, ? extends T> time2)
+    public Filter<R> endedBy(final Expression<? super R, ? extends T> time1,
+                             final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.EndedBy<>(time1, time2);
     }
@@ -918,12 +803,9 @@
      * @param  time1  expression fetching the first temporal value.
      * @param  time2  expression fetching the second temporal value.
      * @return a filter for the "AnyInteracts" operator between the two temporal values.
-     *
-     * @see TemporalOperatorName#ANY_INTERACTS
      */
-    @Override
-    public TemporalOperator<R> anyInteracts(final Expression<? super R, ? extends T> time1,
-                                            final Expression<? super R, ? extends T> time2)
+    public Filter<R> anyInteracts(final Expression<? super R, ? extends T> time1,
+                                  final Expression<? super R, ? extends T> time2)
     {
         return new TemporalFilter.AnyInteracts<>(time1, time2);
     }
@@ -934,10 +816,7 @@
      * @param  operand1  expression fetching the first number.
      * @param  operand2  expression fetching the second number.
      * @return an expression for the "Add" function between the two numerical values.
-     *
-     * @todo Should we really restrict the type to {@link Number}?
      */
-    @Override
     public Expression<R,Number> add(final Expression<? super R, ? extends Number> operand1,
                                     final Expression<? super R, ? extends Number> operand2)
     {
@@ -950,10 +829,7 @@
      * @param  operand1  expression fetching the first number.
      * @param  operand2  expression fetching the second number.
      * @return an expression for the "Subtract" function between the two numerical values.
-     *
-     * @todo Should we really restrict the type to {@link Number}?
      */
-    @Override
     public Expression<R,Number> subtract(final Expression<? super R, ? extends Number> operand1,
                                          final Expression<? super R, ? extends Number> operand2)
     {
@@ -966,10 +842,7 @@
      * @param  operand1  expression fetching the first number.
      * @param  operand2  expression fetching the second number.
      * @return an expression for the "Multiply" function between the two numerical values.
-     *
-     * @todo Should we really restrict the type to {@link Number}?
      */
-    @Override
     public Expression<R,Number> multiply(final Expression<? super R, ? extends Number> operand1,
                                          final Expression<? super R, ? extends Number> operand2)
     {
@@ -982,10 +855,7 @@
      * @param  operand1  expression fetching the first number.
      * @param  operand2  expression fetching the second number.
      * @return an expression for the "Divide" function between the two numerical values.
-     *
-     * @todo Should we really restrict the type to {@link Number}?
      */
-    @Override
     public Expression<R,Number> divide(final Expression<? super R, ? extends Number> operand1,
                                        final Expression<? super R, ? extends Number> operand2)
     {
@@ -993,8 +863,36 @@
     }
 
     /**
+     * Creates an implementation-specific function with a single parameter.
+     *
+     * @param  name       name of the function to call.
+     * @param  parameter  expression providing values for the function argument.
+     * @return an expression which will call the specified function.
+     * @throws IllegalArgumentException if the given name is not recognized,
+     *         or if the argument is illegal for the specified function.
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public Expression<R,?> function(String name, Expression<? super R, ?> parameter) {
+        return function(name, new Expression[] {parameter});
+    }
+
+    /**
+     * Creates an implementation-specific function with two parameters.
+     *
+     * @param  name    name of the function to call.
+     * @param  param1  expression providing values for the first function argument.
+     * @param  param2  expression providing values for the second function argument.
+     * @return an expression which will call the specified function.
+     * @throws IllegalArgumentException if the given name is not recognized,
+     *         or if the arguments are illegal for the specified function.
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public Expression<R,?> function(String name, Expression<? super R, ?> param1, Expression<? super R, ?> param2) {
+        return function(name, new Expression[] {param1, param2});
+    }
+
+    /**
      * Creates an implementation-specific function.
-     * The names of available functions is given by {@link #getCapabilities()}.
      *
      * @param  name        name of the function to call.
      * @param  parameters  expressions providing values for the function arguments.
@@ -1002,7 +900,6 @@
      * @throws IllegalArgumentException if the given name is not recognized,
      *         or if the arguments are illegal for the specified function.
      */
-    @Override
     public Expression<R,?> function(final String name, Expression<? super R, ?>[] parameters) {
         ArgumentChecks.ensureNonNull("name", name);
         ArgumentChecks.ensureNonNull("parameters", parameters);
@@ -1036,18 +933,4 @@
         }
         return register.create(name, parameters);
     }
-
-    /**
-     * Indicates an property by which contents should be sorted, along with intended order.
-     * The given expression should evaluate to {@link Comparable} objects,
-     * but {@link Iterable} objects are accepted as well.
-     *
-     * @param  property  the property to sort by.
-     * @param  order     the sorting order, ascending or descending.
-     * @return definition of sort order of a property.
-     */
-    @Override
-    public SortProperty<R> sort(final ValueReference<? super R, ?> property, final SortOrder order) {
-        return new DefaultSortProperty<>(property, order);
-    }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortProperty.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortProperty.java
index 3c80d20..94402b4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortProperty.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortProperty.java
@@ -24,9 +24,9 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.filter.SortOrder;
-import org.opengis.filter.SortProperty;
-import org.opengis.filter.ValueReference;
+import org.apache.sis.internal.geoapi.filter.SortOrder;
+import org.apache.sis.internal.geoapi.filter.SortProperty;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java
index e497ef1..babbe9e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java
@@ -28,10 +28,8 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.DistanceOperator;
-import org.opengis.filter.DistanceOperatorName;
+import org.apache.sis.internal.geoapi.filter.Literal;
+import org.apache.sis.internal.geoapi.filter.DistanceOperatorName;
 
 
 /**
@@ -46,13 +44,13 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  *
  * @since 1.1
  * @module
  */
-final class DistanceFilter<R,G> extends BinaryGeometryFilter<R,G> implements DistanceOperator<R> {
+final class DistanceFilter<R,G> extends BinaryGeometryFilter<R,G> {
     /**
      * For cross-version compatibility.
      */
@@ -130,7 +128,6 @@
     /**
      * Returns the buffer distance around the geometry that will be used when comparing features geometries.
      */
-    @Override
     public Quantity<Length> getDistance() {
         return distance;
     }
@@ -140,7 +137,6 @@
      *
      * @throws IllegalStateException if the geometry is not a literal.
      */
-    @Override
     public Geometry getGeometry() {
         final Literal<? super R, ? extends GeometryWrapper<G>> literal;
         if (expression2 instanceof Literal<?,?>) {
@@ -156,7 +152,7 @@
     /**
      * Given an object, determines if the test(s) represented by this filter are passed.
      *
-     * @param  object  the object (often a {@link Feature} instance) to evaluate.
+     * @param  object  the object (often a {@code Feature} instance) to evaluate.
      * @return {@code true} if the test(s) are passed for the provided object.
      */
     @Override
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Expression.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Expression.java
new file mode 100644
index 0000000..3d28abf
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/Expression.java
@@ -0,0 +1,74 @@
+/*
+ * 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.filter;
+
+import java.util.List;
+import java.util.function.Function;
+import org.opengis.util.ScopedName;
+
+
+/**
+ * A literal or a named procedure that performs a distinct computation.
+ *
+ * <div class="warning"><b>Upcoming API change</b><br>
+ * This is a placeholder for a GeoAPI 3.1 interface not yet released.
+ * In a future version, all usages of this interface may be replaced
+ * by an interface of the same name but in the {@code org.opengis.filter} package
+ * instead of {@code org.apache.sis.filter}.
+ * </div>
+ *
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
+ * @param  <V>  the type of values computed by the expression.
+ */
+public interface Expression<R,V> extends Function<R,V> {
+    /**
+     * Returns the name of the function to be called.
+     *
+     * @return name of the function to be called.
+     */
+    ScopedName getFunctionName();
+
+    /**
+     * Returns the list sub-expressions that will be evaluated to provide the parameters to the function.
+     *
+     * @return the sub-expressions to be evaluated, or an empty list if none.
+     */
+    List<Expression<? super R, ?>> getParameters();
+
+    /**
+     * Evaluates the expression value based on the content of the given object.
+     *
+     * @param  input  the object to be evaluated by the expression.
+     *         Can be {@code null} if this expression allows null values.
+     * @return value computed by the expression.
+     * @throws NullPointerException if {@code input} is null and this expression requires non-null values.
+     * @throws IllegalArgumentException if the expression can not be applied on the given object.
+     */
+    @Override
+    V apply(R input);
+
+    /**
+     * Returns an expression doing the same evaluation than this method, but returning results
+     * as values of the specified type.
+     *
+     * @param  <N>   compile-time value of {@code type}.
+     * @param  type  desired type of expression results.
+     * @return expression doing the same operation this this expression but with results of the specified type.
+     * @throws ClassCastException if the specified type is not a target type supported by implementation.
+     */
+    <N> Expression<R,N> toValueType(Class<N> type);
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Filter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Filter.java
new file mode 100644
index 0000000..b35a5d7
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/Filter.java
@@ -0,0 +1,81 @@
+/*
+ * 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.filter;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+
+/**
+ * Identification of a subset of resources from a collection of resources
+ * whose property values satisfy a set of logically connected predicates.
+ *
+ * <div class="warning"><b>Upcoming API change</b><br>
+ * This is a placeholder for a GeoAPI 3.1 interface not yet released.
+ * In a future version, all usages of this interface may be replaced
+ * by an interface of the same name but in the {@code org.opengis.filter} package
+ * instead of {@code org.apache.sis.filter}.
+ * </div>
+ */
+public interface Filter<R> extends Predicate<R> {
+    /**
+     * A filter that always evaluates to {@code true}.
+     *
+     * @param  <R>  the type of resources to filter.
+     * @return the "no filtering" filter.
+     */
+    @SuppressWarnings("unchecked")
+    static <R> Filter<R> include() {
+        return FilterLiteral.INCLUDE;
+    }
+
+    /**
+     * A filter that always evaluates to {@code false}.
+     *
+     * @param  <R>  the type of resources to filter.
+     * @return the "exclude all" filter.
+     */
+    @SuppressWarnings("unchecked")
+    static <R> Filter<R> exclude() {
+        return FilterLiteral.EXCLUDE;
+    }
+
+    /**
+     * Returns the nature of the operator.
+     *
+     * @return the nature of this operator.
+     */
+    Enum<?> getOperatorType();
+
+    /**
+     * Returns the expressions used as arguments for this filter.
+     *
+     * @return the expressions used as inputs, or an empty list if none.
+     */
+    List<Expression<? super R, ?>> getExpressions();
+
+    /**
+     * Given an object, determines if the test(s) represented by this filter are passed.
+     *
+     * @param  object  the object (often a {@code Feature} instance) to evaluate.
+     * @return {@code true} if the test(s) are passed for the provided object.
+     * @throws NullPointerException if {@code object} is null.
+     * @throws IllegalArgumentException if the filter can not be applied on the given object.
+     */
+    @Override
+    boolean test(R object);
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/FilterLiteral.java b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterLiteral.java
new file mode 100644
index 0000000..8ca4b9c
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterLiteral.java
@@ -0,0 +1,65 @@
+/*
+ * 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.filter;
+
+import java.util.List;
+import java.util.Collections;
+import java.io.Serializable;
+import java.io.ObjectStreamException;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+final class FilterLiteral implements Filter<Object>, Serializable {
+    @SuppressWarnings("rawtypes")
+    public static final Filter INCLUDE = new FilterLiteral(true);
+
+    @SuppressWarnings("rawtypes")
+    public static final Filter EXCLUDE = new FilterLiteral(false);
+
+    private final boolean value;
+
+    private FilterLiteral(final boolean value) {
+        this.value = value;
+    }
+
+    @Override
+    public Enum<?> getOperatorType() {
+        return value ? FilterName.INCLUDE : FilterName.EXCLUDE;
+    }
+
+    @Override
+    public List<Expression<? super Object, ?>> getExpressions() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public boolean test(Object object) {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        return "Filter." + (value ? "INCLUDE" : "EXCLUDE");
+    }
+
+    private Object readResolve() throws ObjectStreamException {
+        return value ? INCLUDE : EXCLUDE;
+    }
+}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterName.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/filter/FilterName.java
index 2984463..7f23319 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterName.java
@@ -14,24 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.filter;
 
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+enum FilterName {
+     RESOURCE_ID, INCLUDE, EXCLUDE;
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java
index 14c70be..c0eae88 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterNode.java
@@ -19,9 +19,6 @@
 import java.util.function.Predicate;
 import org.apache.sis.internal.filter.Node;
 
-// Branch-dependent imports
-import org.opengis.filter.Filter;
-
 
 /**
  * Base class of some (not all) nodes that are filters. This base class overrides {@link Predicate}
@@ -35,7 +32,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  *
  * @since 1.1
  * @module
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java
index 2a1b8b5..23884ba 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/IdentifierFilter.java
@@ -19,14 +19,10 @@
 import java.util.List;
 import java.util.Collection;
 import java.util.Collections;
+import org.apache.sis.feature.AbstractFeature;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.feature.AttributeConvention;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ResourceId;
-
 
 /**
  * Filter features using a set of predefined identifiers and discarding features
@@ -41,7 +37,7 @@
  * @since 1.1
  * @module
  */
-final class IdentifierFilter<R extends Feature> extends FilterNode<R> implements ResourceId<R> {
+final class IdentifierFilter<R extends AbstractFeature> extends FilterNode<R> {
     /**
      * For cross-version compatibility.
      */
@@ -60,10 +56,14 @@
         this.identifier = identifier;
     }
 
+    @Override
+    public Enum<?> getOperatorType() {
+        return FilterName.RESOURCE_ID;
+    }
+
     /**
      * Returns the identifiers of feature instances to accept.
      */
-    @Override
     public String getIdentifier() {
         return identifier;
     }
@@ -86,7 +86,7 @@
     }
 
     /**
-     * Returns {@code true} if the given object is a {@link Feature} instance and its identifier
+     * Returns {@code true} if the given object is a {@link AbstractFeature} instance and its identifier
      * is one of the identifier specified at {@code IdentifierFilter} construction time.
      */
     @Override
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
index fee716d..14819e8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
@@ -31,9 +31,8 @@
 import org.apache.sis.feature.builder.PropertyTypeBuilder;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.Expression;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -44,7 +43,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <V>  the type of value computed by the expression.
  *
  * @since 1.1
@@ -78,7 +77,7 @@
      * A constant, literal value that can be used in expressions.
      * The {@link #apply(Object)} method ignores the argument and always returns {@link #getValue()}.
      */
-    static class Literal<R,V> extends LeafExpression<R,V> implements org.opengis.filter.Literal<R,V> {
+    static class Literal<R,V> extends LeafExpression<R,V> implements org.apache.sis.internal.geoapi.filter.Literal<R,V> {
         /** For cross-version compatibility. */
         private static final long serialVersionUID = -8383113218490957822L;
 
@@ -129,15 +128,15 @@
 
         /**
          * Provides the type of values returned by {@link #apply(Object)}
-         * wrapped in an {@link AttributeType} named "Literal".
+         * wrapped in an {@link DefaultAttributeType} named "Literal".
          *
          * @param  addTo  where to add the type of properties evaluated by the given expression.
          * @return builder of the added property.
          */
         @Override
-        public PropertyTypeBuilder expectedType(FeatureType ignored, final FeatureTypeBuilder addTo) {
+        public PropertyTypeBuilder expectedType(DefaultFeatureType ignored, final FeatureTypeBuilder addTo) {
             final Class<?> valueType = getValueClass();
-            AttributeType<?> propertyType;
+            DefaultAttributeType<?> propertyType;
             synchronized (TYPES) {
                 propertyType = TYPES.get(valueType);
                 if (propertyType == null) {
@@ -152,17 +151,17 @@
         }
 
         /**
-         * A cache of {@link AttributeType} instances for literal classes. Used for avoiding to create
+         * A cache of {@link DefaultAttributeType} instances for literal classes. Used for avoiding to create
          * duplicated instances when the literal is a common type like {@link String} or {@link Integer}.
          */
         @SuppressWarnings("unchecked")
-        private static final WeakValueHashMap<Class<?>, AttributeType<?>> TYPES = new WeakValueHashMap<>((Class) Class.class);
+        private static final WeakValueHashMap<Class<?>, DefaultAttributeType<?>> TYPES = new WeakValueHashMap<>((Class) Class.class);
 
         /**
          * Invoked when a new attribute type need to be created for the given standard type.
          * The given standard type should be a GeoAPI interface, not the implementation class.
          */
-        private static <R> AttributeType<R> newType(final Class<R> standardType) {
+        private static <R> DefaultAttributeType<R> newType(final Class<R> standardType) {
             return createType(standardType, Names.createLocalName(null, null, "Literal"));
         }
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java
index 96c335c..222c53c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LikeFilter.java
@@ -23,9 +23,7 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.LikeOperator;
+import org.apache.sis.internal.geoapi.filter.ComparisonOperatorName;
 
 
 /**
@@ -35,12 +33,12 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  *
  * @since 1.1
  * @module
  */
-final class LikeFilter<R> extends FilterNode<R> implements LikeOperator<R>, Optimization.OnFilter<R> {
+final class LikeFilter<R> extends FilterNode<R> implements Optimization.OnFilter<R> {
     /**
      * For cross-version compatibility.
      */
@@ -154,6 +152,11 @@
         this.regex          = original.regex;
     }
 
+    @Override
+    public ComparisonOperatorName getOperatorType() {
+        return ComparisonOperatorName.PROPERTY_IS_LIKE;
+    }
+
     /**
      * Creates a new filter of the same type but different parameters.
      */
@@ -185,7 +188,6 @@
      * Returns the pattern character for matching any sequence of characters.
      * For the SQL "{@code LIKE}" operator, this property is the {@code %} character.
      */
-    @Override
     public char getWildCard() {
         return wildcard;
     }
@@ -194,7 +196,6 @@
      * Returns the pattern character for matching exactly one character.
      * For the SQL "{@code LIKE}" operator, this property is the {@code _} character.
      */
-    @Override
     public char getSingleChar() {
         return singleChar;
     }
@@ -203,7 +204,6 @@
      * Returns the pattern character for indicating that the next character should be matched literally.
      * For the SQL "{@code LIKE}" operator, this property is the {@code '} character.
      */
-    @Override
     public char getEscapeChar() {
         return escape;
     }
@@ -211,7 +211,6 @@
     /**
      * Specifies how a filter expression processor should perform string comparisons.
      */
-    @Override
     public boolean isMatchingCase() {
         return isMatchingCase;
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
index 552e311..3c259e9 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
@@ -26,9 +26,8 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.filter.Filter;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
+import org.apache.sis.internal.geoapi.filter.LogicalOperator;
+import org.apache.sis.internal.geoapi.filter.LogicalOperatorName;
 
 
 /**
@@ -39,7 +38,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.2
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  *
  * @since 1.1
  * @module
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
index ccffdb4..7389c23 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
@@ -23,17 +23,14 @@
 import java.util.IdentityHashMap;
 import java.util.ConcurrentModificationException;
 import java.util.function.Predicate;
-import org.opengis.util.CodeList;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.CollectionsExt;
 
 // Branch-dependent imports
-import org.opengis.filter.Filter;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.internal.geoapi.filter.Literal;
+import org.apache.sis.internal.geoapi.filter.LogicalOperator;
+import org.apache.sis.internal.geoapi.filter.LogicalOperatorName;
 
 
 /**
@@ -89,7 +86,7 @@
     /**
      * The type of feature instances to be filtered, or {@code null} if unknown.
      */
-    private FeatureType featureType;
+    private DefaultFeatureType featureType;
 
     /**
      * Filters and expressions already optimized. Also used for avoiding never-ending loops.
@@ -111,12 +108,12 @@
 
     /**
      * Returns the type of feature instances to be filtered, or {@code null} if unknown.
-     * This is the last value specified by a call to {@link #setFeatureType(FeatureType)}.
+     * This is the last value specified by a call to {@link #setFeatureType(DefaultFeatureType)}.
      * The default value is {@code null}.
      *
      * @return the type of feature instances to be filtered, or {@code null} if unknown.
      */
-    public FeatureType getFeatureType() {
+    public DefaultFeatureType getFeatureType() {
         return featureType;
     }
 
@@ -128,7 +125,7 @@
      *
      * @param  type  the type of feature instances to be filtered, or {@code null} if unknown.
      */
-    public void setFeatureType(final FeatureType type) {
+    public void setFeatureType(final DefaultFeatureType type) {
         featureType = type;
     }
 
@@ -428,7 +425,7 @@
         if (filter == null) {
             return Collections.emptyList();
         }
-        final CodeList<?> type = filter.getOperatorType();
+        final Enum<?> type = filter.getOperatorType();
         if (type == LogicalOperatorName.AND) {
             return ((LogicalOperator<R>) filter).getOperands();
         }
@@ -484,7 +481,7 @@
      *
      * @see DefaultFilterFactory#literal(Object)
      */
-    public static <R,V> Literal<R,V> literal(final V value) {
+    public static <R,V> Expression<R,V> literal(final V value) {
         return new LeafExpression.Literal<>(value);
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java b/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java
index ae9d2fc..b70ac74 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java
@@ -31,14 +31,14 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.ValueReference;
+import org.opengis.util.ScopedName;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.internal.geoapi.filter.Name;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
 
 
 /**
@@ -57,8 +57,8 @@
  * @since 1.1
  * @module
  */
-abstract class PropertyValue<V> extends LeafExpression<Feature,V>
-        implements ValueReference<Feature,V>, Optimization.OnExpression<Feature,V>
+abstract class PropertyValue<V> extends LeafExpression<AbstractFeature,V>
+        implements ValueReference<AbstractFeature,V>, Optimization.OnExpression<AbstractFeature,V>
 {
     /**
      * For cross-version compatibility.
@@ -91,6 +91,11 @@
         this.isVirtual = isVirtual;
     }
 
+    @Override
+    public final ScopedName getFunctionName() {
+        return Name.VALUE_REFERENCE;
+    }
+
     /**
      * Creates a new expression retrieving values from a property of the given path.
      * Simple path expressions of the form "a/b/c" can be used.
@@ -102,7 +107,7 @@
      * @throws IllegalArgumentException if the given XPath is not supported.
      */
     @SuppressWarnings("unchecked")
-    static <V> ValueReference<Feature,V> create(String xpath, final Class<V> type) {
+    static <V> ValueReference<AbstractFeature,V> create(String xpath, final Class<V> type) {
         boolean isVirtual = false;
         List<String> path = XPath.split(xpath);
 split:  if (path != null) {
@@ -153,7 +158,7 @@
     }
 
     /**
-     * Returns the type of values fetched from {@link Feature} instance.
+     * Returns the type of values fetched from {@link AbstractFeature} instance.
      * This is the type before conversion to the {@linkplain #getValueClass() target type}.
      * The type is always {@link Object} on newly created expression because the type of feature property
      * values is unknown, but may become a specialized type after {@link Optimization} has been applied.
@@ -219,7 +224,7 @@
          * If no value is found for the given feature, then this method returns {@code null}.
          */
         @Override
-        public Object apply(final Feature instance) {
+        public Object apply(final AbstractFeature instance) {
             return (instance != null) ? instance.getValueOrFallback(name, null) : null;
         }
 
@@ -231,11 +236,11 @@
          */
         @Override
         public PropertyValue<Object> optimize(final Optimization optimization) {
-            final FeatureType type = optimization.getFeatureType();
+            final DefaultFeatureType type = optimization.getFeatureType();
             if (type != null) try {
                 return Features.getLinkTarget(type.getProperty(name))
                         .map((rename) -> new AsObject(rename, isVirtual)).orElse(this);
-            } catch (PropertyNotFoundException e) {
+            } catch (IllegalArgumentException e) {
                 warning(e, true);
             }
             return this;
@@ -280,11 +285,14 @@
          * If no value is found for the given feature, then this method returns {@code null}.
          */
         @Override
-        public V apply(final Feature instance) {
+        public V apply(final AbstractFeature instance) {
             if (instance != null) try {
                 return ObjectConverters.convert(instance.getValueOrFallback(name, null), type);
             } catch (UnconvertibleObjectException e) {
                 warning(e, false);
+            } catch (IllegalArgumentException e) {
+                warning(e, true);
+                // Null will be returned below.
             }
             return null;
         }
@@ -296,7 +304,7 @@
          */
         @Override
         public final PropertyValue<V> optimize(final Optimization optimization) {
-            final FeatureType featureType = optimization.getFeatureType();
+            final DefaultFeatureType featureType = optimization.getFeatureType();
             if (featureType != null) try {
                 /*
                  * Resolve link (e.g. "sis:identifier" as a reference to the real identifier property).
@@ -304,12 +312,12 @@
                  * If there is no renaming to apply (which is the usual case), then `rename` is null.
                  */
                 String rename = name;
-                PropertyType property = featureType.getProperty(rename);
+                AbstractIdentifiedType property = featureType.getProperty(rename);
                 Optional<String> target = Features.getLinkTarget(property);
                 if (target.isPresent()) try {
                     rename = target.get();
                     property = featureType.getProperty(rename);
-                } catch (PropertyNotFoundException e) {
+                } catch (IllegalArgumentException e) {
                     warning(e, true);
                     rename = name;
                 }
@@ -320,8 +328,8 @@
                  */
                 Class<?> source = getSourceClass();
                 final Class<?> original = source;
-                if (property instanceof AttributeType<?>) {
-                    source = ((AttributeType<?>) property).getValueClass();
+                if (property instanceof DefaultAttributeType<?>) {
+                    source = ((DefaultAttributeType<?>) property).getValueClass();
                 }
                 if (!(rename.equals(name) && source.equals(original))) {
                     if (source == Object.class) {
@@ -330,7 +338,7 @@
                         return new CastedAndConverted<>(source, type, rename, isVirtual);
                     }
                 }
-            } catch (PropertyNotFoundException e) {
+            } catch (IllegalArgumentException e) {
                 warning(e, true);
             }
             return this;
@@ -341,7 +349,7 @@
          * when a feature of the given type is evaluated.
          */
         @Override
-        public final PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
+        public final PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
             final PropertyTypeBuilder p = super.expectedType(valueType, addTo);
             if (p instanceof AttributeTypeBuilder<?>) {
                 final AttributeTypeBuilder<?> a = (AttributeTypeBuilder<?>) p;
@@ -362,23 +370,23 @@
      * @throws IllegalArgumentException if this method can not determine the property type for the given feature type.
      */
     @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
-        PropertyType type;
+    public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
+        AbstractIdentifiedType type;
         try {
             type = valueType.getProperty(name);
-        } catch (PropertyNotFoundException e) {
+        } catch (IllegalArgumentException e) {
             if (isVirtual) {
                 // The property does not exist but may be defined on a yet unknown child type.
                 return expectedType(addTo);
             }
             throw e;
         }
-        while (type instanceof Operation) {
-            final IdentifiedType result = ((Operation) type).getResult();
-            if (result != type && result instanceof PropertyType) {
-                type = (PropertyType) result;
-            } else if (result instanceof FeatureType) {
-                return addTo.addAssociation((FeatureType) result).setName(name);
+        while (type instanceof AbstractOperation) {
+            final AbstractIdentifiedType result = ((AbstractOperation) type).getResult();
+            if (result != type && result instanceof AbstractIdentifiedType) {
+                type = (AbstractIdentifiedType) result;
+            } else if (result instanceof DefaultFeatureType) {
+                return addTo.addAssociation((DefaultFeatureType) result).setName(name);
             } else {
                 return null;
             }
@@ -412,7 +420,7 @@
         }
 
         /**
-         * Returns the type of values fetched from {@link Feature} instance.
+         * Returns the type of values fetched from {@link AbstractFeature} instance.
          */
         @Override
         protected Class<S> getSourceClass() {
@@ -424,11 +432,13 @@
          * If no value is found for the given feature, then this method returns {@code null}.
          */
         @Override
-        public V apply(final Feature instance) {
+        public V apply(final AbstractFeature instance) {
             if (instance != null) try {
                 return converter.apply(source.cast(instance.getValueOrFallback(name, null)));
             } catch (ClassCastException | UnconvertibleObjectException e) {
                 warning(e, false);
+            } catch (IllegalArgumentException e) {
+                warning(e, true);
             }
             return null;
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFilter.java
index a242531..8af079c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFilter.java
@@ -20,11 +20,8 @@
 import java.time.Instant;
 
 // Branch-dependent imports
-import org.opengis.temporal.Period;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.TemporalOperator;
-import org.opengis.filter.TemporalOperatorName;
+import org.apache.sis.internal.geoapi.temporal.Period;
+import org.apache.sis.internal.geoapi.filter.TemporalOperatorName;
 
 
 /**
@@ -42,13 +39,13 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <T>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <T>  the type of resources (e.g. {@code Feature}) used as inputs.
  *
  * @since 1.1
  * @module
  */
 abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
-        implements TemporalOperator<T>, Optimization.OnFilter<T>
+        implements Filter<T>, Optimization.OnFilter<T>
 {
     /**
      * For cross-version compatibility.
@@ -69,12 +66,12 @@
 
     /**
      * Converts a GeoAPI instant to a Java instant. This is a temporary method
-     * to be removed after we revisited {@link org.opengis.temporal} package.
+     * to be removed after we revisited {@link org.apache.sis.internal.geoapi.temporal} package.
      *
      * @param  instant  the GeoAPI instant, or {@code null}.
      * @return the Java instant, or {@code null}.
      */
-    private static Instant toInstant(final org.opengis.temporal.Instant instant) {
+    private static Instant toInstant(final org.apache.sis.internal.geoapi.temporal.Instant instant) {
         if (instant != null) {
             final Date t = instant.getDate();
             if (t != null) {
@@ -88,7 +85,7 @@
      * Returns {@code true} if {@code self} is non null and before {@code other}.
      * This is an helper function for {@code evaluate(…)} methods implementations.
      */
-    private static boolean isBefore(final org.opengis.temporal.Instant self, final Instant other) {
+    private static boolean isBefore(final org.apache.sis.internal.geoapi.temporal.Instant self, final Instant other) {
         final Instant t = toInstant(self);
         return (t != null) && t.isBefore(other);
     }
@@ -97,7 +94,7 @@
      * Returns {@code true} if {@code self} is non null and after {@code other}.
      * This is an helper function for {@code evaluate(…)} methods implementations.
      */
-    private static boolean isAfter(final org.opengis.temporal.Instant self, final Instant other) {
+    private static boolean isAfter(final org.apache.sis.internal.geoapi.temporal.Instant self, final Instant other) {
         final Instant t = toInstant(self);
         return (t != null) && t.isAfter(other);
     }
@@ -106,7 +103,7 @@
      * Returns {@code true} if {@code self} is non null and equal to {@code other}.
      * This is an helper function for {@code evaluate(…)} methods implementations.
      */
-    private static boolean isEqual(final org.opengis.temporal.Instant self, final Instant other) {
+    private static boolean isEqual(final org.apache.sis.internal.geoapi.temporal.Instant self, final Instant other) {
         final Instant t = toInstant(self);
         return (t != null) && t.equals(other);
     }
@@ -115,8 +112,8 @@
      * Returns {@code true} if {@code self} is non null and before {@code other}.
      * This is an helper function for {@code evaluate(…)} methods implementations.
      */
-    private static boolean isBefore(final org.opengis.temporal.Instant self,
-                                    final org.opengis.temporal.Instant other)
+    private static boolean isBefore(final org.apache.sis.internal.geoapi.temporal.Instant self,
+                                    final org.apache.sis.internal.geoapi.temporal.Instant other)
     {
         final Instant t, o;
         return ((t = toInstant(self)) != null) && ((o = toInstant(other)) != null) && t.isBefore(o);
@@ -126,8 +123,8 @@
      * Returns {@code true} if {@code self} is non null and after {@code other}.
      * This is an helper function for {@code evaluate(…)} methods implementations.
      */
-    private static boolean isAfter(final org.opengis.temporal.Instant self,
-                                   final org.opengis.temporal.Instant other)
+    private static boolean isAfter(final org.apache.sis.internal.geoapi.temporal.Instant self,
+                                   final org.apache.sis.internal.geoapi.temporal.Instant other)
     {
         final Instant t, o;
         return ((t = toInstant(self)) != null) && ((o = toInstant(other)) != null) && t.isAfter(o);
@@ -137,8 +134,8 @@
      * Returns {@code true} if {@code self} is non null and equal to {@code other}.
      * This is an helper function for {@code evaluate(…)} methods implementations.
      */
-    private static boolean isEqual(final org.opengis.temporal.Instant self,
-                                   final org.opengis.temporal.Instant other)
+    private static boolean isEqual(final org.apache.sis.internal.geoapi.temporal.Instant self,
+                                   final org.apache.sis.internal.geoapi.temporal.Instant other)
     {
         final Instant t = toInstant(self);
         return (t != null) && t.equals(toInstant(other));
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
index 1b5c792..ee48cc4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
@@ -25,10 +25,7 @@
 import org.apache.sis.internal.filter.Node;
 
 // Branch-dependent imports
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.NilOperator;
-import org.opengis.filter.NullOperator;
+import org.apache.sis.internal.geoapi.filter.ComparisonOperatorName;
 
 
 /**
@@ -39,7 +36,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <V>  the type of value computed by the expression.
  *
  * @since 1.1
@@ -100,7 +97,7 @@
      * {@code null}.
      */
     static final class IsNull<R> extends UnaryFunction<R,Object>
-            implements NullOperator<R>, Optimization.OnFilter<R>
+            implements Filter<R>, Optimization.OnFilter<R>
  {
         /** For cross-version compatibility. */
         private static final long serialVersionUID = 2960285515924533419L;
@@ -110,6 +107,10 @@
             super(expression);
         }
 
+        @Override public ComparisonOperatorName getOperatorType() {
+            return ComparisonOperatorName.PROPERTY_IS_NULL;
+        }
+
         /** Creates a new filter of the same type but different parameters. */
         @Override public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
             return new IsNull<>(effective[0]);
@@ -133,7 +134,7 @@
      * can not be provided for the reason given by {@link #getNilReason()}.
      */
     static final class IsNil<R> extends UnaryFunction<R,Object>
-            implements NilOperator<R>, Optimization.OnFilter<R>
+            implements Filter<R>, Optimization.OnFilter<R>
     {
         /** For cross-version compatibility. */
         private static final long serialVersionUID = -7540765433296725888L;
@@ -147,13 +148,17 @@
             this.nilReason = nilReason;
         }
 
+        @Override public ComparisonOperatorName getOperatorType() {
+            return ComparisonOperatorName.PROPERTY_IS_NIL;
+        }
+
         /** Creates a new filter of the same type but different parameters. */
         @Override public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
             return new IsNil<>(effective[0], nilReason);
         }
 
         /** Returns the reason why the value is nil. */
-        @Override public Optional<String> getNilReason() {
+        public Optional<String> getNilReason() {
             return Optional.ofNullable(nilReason);
         }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
index 10d45ca..a10c8df 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
@@ -34,7 +34,7 @@
  *       <li>For other operations, the common CRS is chosen by
  *         {@linkplain org.apache.sis.referencing.CRS#suggestCommonTarget referencing utility method}.
  *         If that method cannot provide a common space,
- *         then an {@link org.opengis.filter.InvalidFilterValueException} is thrown.</li>
+ *         then an {@link java.lang.IllegalArgumentException} is thrown.</li>
  *     </ul>
  *   </li>
  * </ul>
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/BandedIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/BandedIterator.java
index 997230b..1c79b51 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/BandedIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/BandedIterator.java
@@ -28,7 +28,6 @@
 import java.awt.image.RenderedImage;
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
-import org.opengis.coverage.grid.SequenceType;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java b/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java
index 63ef0bd..b094468 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java
@@ -74,7 +74,7 @@
      *
      * Values in {@code source} buffer are always given with band index varying fastest, then column index,
      * then row index. Columns are traversed from left to right and rows are traversed from top to bottom
-     * ({@link org.opengis.coverage.grid.SequenceType#LINEAR} iteration order).
+     * ({@link org.apache.sis.image.SequenceType#LINEAR} iteration order).
      *
      * <p>The interpolation point is in the middle. For example if the {@linkplain #getSupportSize() support size}
      * is 4×4 pixels, then the interpolation point is the dot below and the fractional coordinates are relative to
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
index d6c4177..c70e021 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
@@ -37,7 +37,6 @@
 import java.awt.image.SinglePixelPackedSampleModel;
 import java.awt.image.RasterFormatException;
 import java.util.NoSuchElementException;
-import org.opengis.coverage.grid.SequenceType;
 import org.apache.sis.util.resources.Messages;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
@@ -1098,7 +1097,7 @@
      * <var>(number of bands)</var> × <var>(window width)</var> × <var>(window height)</var>.
      * Values are always stored with band index varying fastest, then column index, then row index.
      * Columns are traversed from left to right and rows are traversed from top to bottom
-     * ({@link SequenceType#LINEAR} iteration order).
+     * (linear iteration order).
      * That order is the same regardless the {@linkplain #getIterationOrder() iteration order} of this iterator.
      *
      * <div class="note"><b>Example:</b>
@@ -1119,7 +1118,7 @@
      * <div class="note"><b>Usage example:</b>
      * following code creates an iterator over the full area of given image, then a window of 5×5 pixels.
      * The window is moved over all the image area in iteration order. Inside the window, data are copied
-     * in {@linkplain SequenceType#LINEAR linear order} regardless the iteration order.
+     * in linear order regardless the iteration order.
      *
      * {@preformat java
      *     PixelIterator it = create(image, null, new Dimension(5, 5), null);     // Windows size will be 5×5 pixels.
@@ -1198,8 +1197,8 @@
          * capacity is <var>(number of bands)</var> × <var>(window width)</var> × <var>(window height)</var>.
          * Values are always stored with band index varying fastest, then column index, then row index.
          * Columns are traversed from left to right and rows are traversed from top to bottom
-         * ({@link SequenceType#LINEAR} iteration order).
-         * That order is the same regardless the {@linkplain PixelIterator#getIterationOrder() iteration order}
+         * (linear iteration order).
+         * That order is the same regardless the iteration order
          * of enclosing iterator.
          *
          * <p>Every time that {@link #update()} is invoked, the buffer content is replaced by sample values
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/SequenceType.java b/core/sis-feature/src/main/java/org/apache/sis/image/SequenceType.java
new file mode 100644
index 0000000..b31a22b
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/SequenceType.java
@@ -0,0 +1,38 @@
+/*
+ * 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.image;
+
+
+/**
+ * Specifies the order in which attribute value records are assigned to grid points.
+ * Placeholder for {@code org.opengis.coverage.grid.SequenceType}.
+ *
+ * <div class="note"><b>Upcoming API change:</b>
+ * this class may move to GeoAPI in a future version. If that move happens,
+ * the {@code org.apache.sis.image} package name would become {@code org.opengis.coverage}.</div>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public enum SequenceType {
+    /**
+     * Iterate consecutive grid points along complete grid lines.
+     */
+    LINEAR
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/WritablePixelIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/WritablePixelIterator.java
index 986c354..cef0b99 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/WritablePixelIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/WritablePixelIterator.java
@@ -23,7 +23,6 @@
 import java.awt.image.RenderedImage;
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
-import org.opengis.coverage.grid.SequenceType;
 import org.apache.sis.internal.feature.Resources;
 
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
index 47a6267..50b461a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
@@ -26,15 +26,11 @@
 import org.apache.sis.feature.Features;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyNotFoundException;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -106,9 +102,9 @@
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} can be
      * the {@link com.esri.core.geometry.Geometry} class from ESRI's API, or the {@code Geometry} class from
      * <cite>Java Topology Suite</cite> (JTS) library, or any other class defined in future SIS versions.
-     * See {@link #isGeometryAttribute(IdentifiedType)} for testing whether the value is a supported type.</p>
+     * See {@code isGeometryAttribute(IdentifiedType)} for testing whether the value is a supported type.</p>
      *
-     * @see #isGeometryAttribute(IdentifiedType)
+     * @see #isGeometryAttribute(AbstractIdentifiedType)
      */
     public static final ScopedName GEOMETRY_PROPERTY;
 
@@ -140,7 +136,7 @@
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
      * {@link org.opengis.referencing.crs.CoordinateReferenceSystem}.</p>
      *
-     * @see #getCRSCharacteristic(Property)
+     * @see #getCRSCharacteristic(Object)
      */
     public static final ScopedName CRS_CHARACTERISTIC;
 
@@ -170,7 +166,7 @@
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
      * {@link Integer}.</p>
      *
-     * @see #getMaximalLengthCharacteristic(Property)
+     * @see #getMaximalLengthCharacteristic(Object)
      */
     public static final ScopedName MAXIMAL_LENGTH_CHARACTERISTIC;
 
@@ -195,13 +191,13 @@
 
     /**
      * String representation of the {@link #IDENTIFIER_PROPERTY} name.
-     * This can be used in calls to {@link Feature#getPropertyValue(String)}.
+     * This can be used in calls to {@link AbstractFeature#getPropertyValue(String)}.
      */
     public static final String IDENTIFIER = "sis:identifier";
 
     /**
      * String representation of the {@link #GEOMETRY_PROPERTY} name.
-     * This can be used in calls to {@link Feature#getPropertyValue(String)}.
+     * This can be used in calls to {@link AbstractFeature#getPropertyValue(String)}.
      */
     public static final String GEOMETRY = "sis:geometry";
 
@@ -261,17 +257,17 @@
      * @param  feature  the feature type to test, or {@code null}.
      * @return whether the given feature type is non-null and has a {@value #IDENTIFIER} property.
      */
-    public static boolean hasIdentifier(final FeatureType feature) {
+    public static boolean hasIdentifier(final DefaultFeatureType feature) {
         if (feature != null) try {
             return feature.getProperty(IDENTIFIER) != null;
-        } catch (PropertyNotFoundException e) {
+        } catch (IllegalArgumentException e) {
             // Ignore
         }
         return false;
     }
 
     /**
-     * Returns {@code true} if the given type is an {@link AttributeType} or an {@link Operation} computing
+     * Returns {@code true} if the given type is an {@code AttributeType} or an {@code Operation} computing
      * an attribute, and the attribute value is one of the geometry types recognized by SIS.
      * The types currently recognized by SIS are:
      *
@@ -287,8 +283,8 @@
      *
      * @see #GEOMETRY_PROPERTY
      */
-    public static boolean isGeometryAttribute(final IdentifiedType type) {
-        final Optional<AttributeType<?>> at = Features.toAttribute(type);
+    public static boolean isGeometryAttribute(final AbstractIdentifiedType type) {
+        final Optional<DefaultAttributeType<?>> at = Features.toAttribute(type);
         return at.isPresent() && Geometries.isKnownType(at.get().getValueClass());
     }
 
@@ -300,7 +296,7 @@
      * @param  type  the operation or attribute type for which to get the CRS, or {@code null}.
      * @return {@code true} if a characteristic for Coordinate Reference System has been found.
      */
-    public static boolean characterizedByCRS(final IdentifiedType type) {
+    public static boolean characterizedByCRS(final AbstractIdentifiedType type) {
         return hasCharacteristic(type, CRS, CoordinateReferenceSystem.class);
     }
 
@@ -315,7 +311,7 @@
      *
      * @see org.apache.sis.feature.builder.AttributeTypeBuilder#setCRS(CoordinateReferenceSystem)
      */
-    public static CoordinateReferenceSystem getCRSCharacteristic(final Property attribute) {
+    public static CoordinateReferenceSystem getCRSCharacteristic(final Object attribute) {
         return (CoordinateReferenceSystem) getCharacteristic(attribute, CRS);
     }
 
@@ -325,7 +321,7 @@
      * If the given property is a link, then this method follows the link in the given feature type (if non-null).
      *
      * <p>This method should be used only when the actual property instance is unknown.
-     * Otherwise, {@link #getCRSCharacteristic(Property)} should be used because the CRS
+     * Otherwise, {@code getCRSCharacteristic(Property)} should be used because the CRS
      * may vary for each property instance.</p>
      *
      * @param  feature    the feature type in which to follow links, or {@code null} if none.
@@ -334,7 +330,7 @@
      * @throws ClassCastException if {@link #CRS_CHARACTERISTIC} has been found but is associated
      *         to an object which is not a {@link CoordinateReferenceSystem} instance.
      */
-    public static CoordinateReferenceSystem getCRSCharacteristic(final FeatureType feature, final PropertyType attribute) {
+    public static CoordinateReferenceSystem getCRSCharacteristic(final DefaultFeatureType feature, final AbstractIdentifiedType attribute) {
         return (CoordinateReferenceSystem) getCharacteristic(feature, attribute, CRS);
     }
 
@@ -346,7 +342,7 @@
      * @param  type  the operation or attribute type for which to get the maximal length, or {@code null}.
      * @return {@code true} if a characteristic for maximal length has been found.
      */
-    public static boolean characterizedByMaximalLength(final IdentifiedType type) {
+    public static boolean characterizedByMaximalLength(final AbstractIdentifiedType type) {
         return hasCharacteristic(type, MAXIMAL_LENGTH, Integer.class);
     }
 
@@ -361,7 +357,7 @@
      *
      * @see org.apache.sis.feature.builder.AttributeTypeBuilder#setMaximalLength(Integer)
      */
-    public static Integer getMaximalLengthCharacteristic(final Property attribute) {
+    public static Integer getMaximalLengthCharacteristic(final Object attribute) {
         return (Integer) getCharacteristic(attribute, MAXIMAL_LENGTH);
     }
 
@@ -371,7 +367,7 @@
      * If the given property is a link, then this method follows the link in the given feature type (if non-null).
      *
      * <p>This method should be used only when the actual property instance is unknown.
-     * Otherwise, {@link #getMaximalLengthCharacteristic(Property)} should be used because
+     * Otherwise, {@code getMaximalLengthCharacteristic(Property)} should be used because
      * the maximal length may vary for each property instance.</p>
      *
      * @param  feature    the feature type in which to follow links, or {@code null} if none.
@@ -380,7 +376,7 @@
      * @throws ClassCastException if {@link #MAXIMAL_LENGTH_CHARACTERISTIC} has been found but is associated
      *         to an object which is not a {@link CoordinateReferenceSystem} instance.
      */
-    public static Integer getMaximalLengthCharacteristic(final FeatureType feature, final PropertyType attribute) {
+    public static Integer getMaximalLengthCharacteristic(final DefaultFeatureType feature, final AbstractIdentifiedType attribute) {
         return (Integer) getCharacteristic(feature, attribute, MAXIMAL_LENGTH);
     }
 
@@ -393,10 +389,10 @@
      * @param  valueClass  the expected characteristic values.
      * @return {@code true} if a characteristic of the given name exists and has values assignable to the given class.
      */
-    private static boolean hasCharacteristic(IdentifiedType type, final String name, final Class<?> valueClass) {
-        final Optional<AttributeType<?>> at = Features.toAttribute(type);
+    private static boolean hasCharacteristic(AbstractIdentifiedType type, final String name, final Class<?> valueClass) {
+        final Optional<DefaultAttributeType<?>> at = Features.toAttribute(type);
         if (at.isPresent()) {
-            final AttributeType<?> ct = at.get().characteristics().get(name);
+            final DefaultAttributeType<?> ct = at.get().characteristics().get(name);
             if (ct != null) {
                 return valueClass.isAssignableFrom(ct.getValueClass());
             }
@@ -413,16 +409,16 @@
      * @param  name       name of the characteristic to get.
      * @return the value or default value of the given characteristic in the given property, or {@code null} if none.
      */
-    private static Object getCharacteristic(final Property attribute, final String name) {
-        if (attribute instanceof Attribute<?>) {
-            final Attribute<?> at = ((Attribute<?>) attribute).characteristics().get(name);
+    private static Object getCharacteristic(final Object attribute, final String name) {
+        if (attribute instanceof AbstractAttribute<?>) {
+            final AbstractAttribute<?> at = ((AbstractAttribute<?>) attribute).characteristics().get(name);
             if (at != null) {
                 final Object value = at.getValue();
                 if (value != null) {
                     return value;
                 }
             }
-            final AttributeType<?> type = ((Attribute<?>) attribute).getType().characteristics().get(name);
+            final DefaultAttributeType<?> type = ((AbstractAttribute<?>) attribute).getType().characteristics().get(name);
             if (type != null) {
                 return type.getDefaultValue();
             }
@@ -440,13 +436,13 @@
      * @param  characteristic  name of the characteristic from which to get the default value.
      * @return the default value of the named characteristic in the given property, or {@code null} if none.
      */
-    private static Object getCharacteristic(final FeatureType feature, PropertyType property, final String characteristic) {
+    private static Object getCharacteristic(final DefaultFeatureType feature, AbstractIdentifiedType property, final String characteristic) {
         final Optional<String> referent = Features.getLinkTarget(property);
         if (referent.isPresent() && feature != null) {
             property = feature.getProperty(referent.get());
         }
-        if (property instanceof AttributeType<?>) {
-            final AttributeType<?> type = ((AttributeType<?>) property).characteristics().get(characteristic);
+        if (property instanceof DefaultAttributeType<?>) {
+            final DefaultAttributeType<?> type = ((DefaultAttributeType<?>) property).characteristics().get(characteristic);
             if (type != null) {
                 return type.getDefaultValue();
             }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
index f1572b8..6a1ba54 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
@@ -16,15 +16,14 @@
  */
 package org.apache.sis.internal.feature;
 
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
+import org.apache.sis.filter.Expression;
 import org.apache.sis.filter.Optimization;
 import org.apache.sis.filter.DefaultFilterFactory;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
 import org.apache.sis.feature.builder.PropertyTypeBuilder;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.internal.geoapi.filter.Literal;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
 
 
 /**
@@ -38,7 +37,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.2
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <V>  the type of values computed by the expression.
  *
  * @since 1.0
@@ -66,7 +65,7 @@
      * @throws IllegalArgumentException if this method can operate only on some feature types
      *         and the given type is not one of them.
      */
-    PropertyTypeBuilder expectedType(FeatureType valueType, FeatureTypeBuilder addTo);
+    PropertyTypeBuilder expectedType(DefaultFeatureType valueType, FeatureTypeBuilder addTo);
 
     /**
      * Tries to cast or convert the given expression to a {@link FeatureExpression}.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java
index 9bed7a3..0116c2c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java
@@ -30,7 +30,7 @@
 import org.apache.sis.util.Static;
 
 // Branch-dependent imports
-import org.opengis.feature.PropertyType;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -71,14 +71,14 @@
      * @param  properties  the properties for which to get the names, or {@code null}.
      * @return the name of all given properties, or {@code null} if the given list was null.
      */
-    public static String[] getNames(final Collection<? extends PropertyType> properties) {
+    public static String[] getNames(final Collection<? extends AbstractIdentifiedType> properties) {
         if (properties == null) {
             return null;
         }
         final String[] names = new String[properties.size()];
-        final Iterator<? extends PropertyType> it = properties.iterator();
+        final Iterator<? extends AbstractIdentifiedType> it = properties.iterator();
         for (int i=0; i < names.length; i++) {
-            final PropertyType property = it.next();
+            final AbstractIdentifiedType property = it.next();
             if (property != null) {
                 final GenericName name = property.getName();
                 if (name != null) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
index 826a267..29a2e09 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.internal.feature;
 
-import java.util.Set;
 import java.util.Objects;
 import java.util.Iterator;
 import java.util.OptionalInt;
@@ -25,10 +24,7 @@
 import javax.measure.quantity.Length;
 import javax.measure.IncommensurableException;
 import org.opengis.geometry.Geometry;
-import org.opengis.geometry.Boundary;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.geometry.TransfiniteSet;
-import org.opengis.geometry.complex.Complex;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.TransformException;
@@ -44,9 +40,8 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.DistanceOperatorName;
-import org.opengis.filter.InvalidFilterValueException;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.internal.geoapi.filter.DistanceOperatorName;
 
 
 /**
@@ -118,7 +113,6 @@
      * @return the geometry CRS, or {@code null} if unknown.
      * @throws BackingStoreException if the CRS is defined by a SRID code and that code can not be used.
      */
-    @Override
     public abstract CoordinateReferenceSystem getCoordinateReferenceSystem();
 
     /**
@@ -138,7 +132,6 @@
      * @return the geometry envelope. Should never be {@code null}.
      *         Note though that for an empty geometry or a single point, the returned envelope will be empty.
      */
-    @Override
     public abstract GeneralEnvelope getEnvelope();
 
     /**
@@ -148,7 +141,6 @@
      *
      * @todo Consider a {@code getCentroid2D()} method avoiding the cost of fetching the CRS.
      */
-    @Override
     public abstract DirectPosition getCentroid();
 
     /**
@@ -201,7 +193,7 @@
      * @param  context   the preferred CRS and other context to use if geometry transformations are needed.
      * @return result of applying the specified predicate.
      * @throws UnsupportedOperationException if the operation can not be performed with current implementation.
-     * @throws InvalidFilterValueException if an error occurred while executing the operation on given geometries.
+     * @throws IllegalArgumentException if an error occurred while executing the operation on given geometries.
      */
     public final boolean predicate(final DistanceOperatorName type, final GeometryWrapper<G> other,
                                    final Quantity<Length> distance, final SpatialOperationContext context)
@@ -218,7 +210,7 @@
                 return geometries[0].predicateSameCRS(type, geometries[1], dv);
             }
         } catch (FactoryException | TransformException | IncommensurableException e) {
-            throw new InvalidFilterValueException(e);
+            throw new IllegalArgumentException(e);
         }
         /*
          * No common CRS. Consider that we have no intersection, no overlap, etc.
@@ -239,7 +231,7 @@
      * @param  context  the preferred CRS and other context to use if geometry transformations are needed.
      * @return result of applying the specified predicate.
      * @throws UnsupportedOperationException if the operation can not be performed with current implementation.
-     * @throws InvalidFilterValueException if an error occurred while executing the operation on given geometries.
+     * @throws IllegalArgumentException if an error occurred while executing the operation on given geometries.
      */
     public final boolean predicate(final SpatialOperatorName type, final GeometryWrapper<G> other,
                                    final SpatialOperationContext context)
@@ -251,7 +243,7 @@
                 return geometries[0].predicateSameCRS(type, geometries[1]);
             }
         } catch (FactoryException | TransformException | IncommensurableException e) {
-            throw new InvalidFilterValueException(e);
+            throw new IllegalArgumentException(e);
         }
         /*
          * No common CRS. Consider that we have no intersection, no overlap, etc.
@@ -493,7 +485,6 @@
      *
      * @see #getCoordinateReferenceSystem()
      */
-    @Override
     public GeometryWrapper<G> transform(final CoordinateReferenceSystem targetCRS) throws TransformException {
         if (targetCRS == null) {
             return this;
@@ -530,33 +521,6 @@
     public abstract String formatWKT(double flatness);
 
     /**
-     * Methods from the {@link Geometry} interface. The {@link Override} annotation is intentionally omitted
-     * for reducing the risk of compilation failures during the upcoming revision of GeoAPI interfaces since
-     * some of those methods will be removed.
-     */
-    @Deprecated public final Geometry       getMbRegion()                             {throw new UnsupportedOperationException();}
-    @Deprecated public final DirectPosition getRepresentativePoint()                  {throw new UnsupportedOperationException();}
-    @Deprecated public final Boundary       getBoundary()                             {throw new UnsupportedOperationException();}
-    @Deprecated public final Complex        getClosure()                              {throw new UnsupportedOperationException();}
-    @Deprecated public final boolean        isSimple()                                {throw new UnsupportedOperationException();}
-    @Deprecated public final boolean        isCycle()                                 {throw new UnsupportedOperationException();}
-    @Deprecated public final double         distance(Geometry geometry)               {throw new UnsupportedOperationException();}
-    @Deprecated public final int            getDimension(DirectPosition point)        {throw new UnsupportedOperationException();}
-    @Deprecated public final int            getCoordinateDimension()                  {throw new UnsupportedOperationException();}
-    @Deprecated public final Set<Complex>   getMaximalComplex()                       {throw new UnsupportedOperationException();}
-    @Deprecated public final Geometry       getConvexHull()                           {throw new UnsupportedOperationException();}
-    @Deprecated public final Geometry       getBuffer(double distance)                {throw new UnsupportedOperationException();}
-    @Deprecated public final Geometry       clone() throws CloneNotSupportedException {throw new CloneNotSupportedException();}
-    @Deprecated public final boolean        contains(TransfiniteSet pointSet)         {throw new UnsupportedOperationException();}
-    @Deprecated public final boolean        contains(DirectPosition point)            {throw new UnsupportedOperationException();}
-    @Deprecated public final boolean        intersects(TransfiniteSet pointSet)       {throw new UnsupportedOperationException();}
-    @Deprecated public final boolean        equals(TransfiniteSet pointSet)           {throw new UnsupportedOperationException();}
-    @Deprecated public final TransfiniteSet union(TransfiniteSet pointSet)            {throw new UnsupportedOperationException();}
-    @Deprecated public final TransfiniteSet intersection(TransfiniteSet pointSet)     {throw new UnsupportedOperationException();}
-    @Deprecated public final TransfiniteSet difference(TransfiniteSet pointSet)       {throw new UnsupportedOperationException();}
-    @Deprecated public final TransfiniteSet symmetricDifference(TransfiniteSet ps)    {throw new UnsupportedOperationException();}
-
-    /**
      * Returns {@code true} if the given object is a wrapper of the same class
      * and the wrapped geometry implementations are equal.
      *
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeatures.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeatures.java
index 603ba0e..df960c1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeatures.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeatures.java
@@ -28,8 +28,7 @@
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 
 // Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.AbstractAttribute;
 
 
 /**
@@ -48,14 +47,14 @@
      * Definition of characteristics containing a list of instants, without duplicates.
      * Should be in chronological order, but this is not verified.
      */
-    public static final AttributeType<Instant> TIME_AS_INSTANTS;
+    public static final DefaultAttributeType<Instant> TIME_AS_INSTANTS;
 
     /**
      * An alternative to {@link #TIME_AS_INSTANTS} used when times can not be mapped to calendar dates.
      * This characteristic uses the same name than {@code TIME_AS_INSTANTS}. Consequently at most one
      * of {@code TIME_AS_INSTANTS} and {@code TIME_AS_NUMBERS} can be used on the same property.
      */
-    private static final AttributeType<Number> TIME_AS_NUMBERS;
+    private static final DefaultAttributeType<Number> TIME_AS_NUMBERS;
     static {
         final LocalName scope = Names.createLocalName("OGC", null, "MF");
         final Map<String,Object> properties = Collections.singletonMap(
@@ -72,7 +71,7 @@
      * @param  hasCRS  whether a temporal CRS is available.
      * @return the "datetimes" characteristic.
      */
-    public static AttributeType<?> characteristic(final boolean hasCRS) {
+    public static DefaultAttributeType<?> characteristic(final boolean hasCRS) {
         return hasCRS ? TIME_AS_INSTANTS : TIME_AS_NUMBERS;
     }
 
@@ -98,8 +97,8 @@
      * @param  dest    the attribute on which to set time characteristic.
      * @param  millis  times in milliseconds since the epoch.
      */
-    public final void setInstants(final Attribute<?> dest, final long[] millis) {
-        final Attribute<Instant> c = TIME_AS_INSTANTS.newInstance();
+    public final void setInstants(final AbstractAttribute<?> dest, final long[] millis) {
+        final AbstractAttribute<Instant> c = TIME_AS_INSTANTS.newInstance();
         c.setValues(cache.computeIfAbsent(InstantList.vectorize(millis), InstantList::new));
         dest.characteristics().values().add(c);
     }
@@ -116,18 +115,18 @@
      * @param  values     times in arbitrary units since an arbitrary epoch.
      * @param  converter  the CRS to use for converting values to {@link Instant} instances, or {@code null}.
      */
-    public static void setTimes(final Attribute<?> dest, final Vector values, final DefaultTemporalCRS converter) {
-        final Attribute<?> ct;
+    public static void setTimes(final AbstractAttribute<?> dest, final Vector values, final DefaultTemporalCRS converter) {
+        final AbstractAttribute<?> ct;
         if (converter != null) {
             final Instant[] instants = new Instant[values.size()];
             for (int i=0; i<instants.length; i++) {
                 instants[i] = converter.toInstant(values.doubleValue(i));
             }
-            final Attribute<Instant> c = TIME_AS_INSTANTS.newInstance();
+            final AbstractAttribute<Instant> c = TIME_AS_INSTANTS.newInstance();
             c.setValues(UnmodifiableArrayList.wrap(instants));
             ct = c;
         } else {
-            final Attribute<Number> c = TIME_AS_NUMBERS.newInstance();
+            final AbstractAttribute<Number> c = TIME_AS_NUMBERS.newInstance();
             c.setValues(values);
             ct = c;
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/SpatialOperationContext.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/SpatialOperationContext.java
index 82d0900..4c380a8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/SpatialOperationContext.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/SpatialOperationContext.java
@@ -49,8 +49,8 @@
 import org.apache.sis.metadata.iso.citation.Citations;
 
 // Branch-dependent imports
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.DistanceOperatorName;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.internal.geoapi.filter.DistanceOperatorName;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/esri/Wrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/esri/Wrapper.java
index 60dc2b7..33a1a34 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/esri/Wrapper.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/esri/Wrapper.java
@@ -45,7 +45,7 @@
 import org.apache.sis.util.Debug;
 
 // Branch-dependent imports
-import org.opengis.filter.SpatialOperatorName;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PointWrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PointWrapper.java
index 7c238a7..a51b96b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PointWrapper.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PointWrapper.java
@@ -31,7 +31,9 @@
 import org.apache.sis.internal.feature.GeometryWrapper;
 import org.apache.sis.internal.filter.sqlmm.SQLMM;
 import org.apache.sis.util.Debug;
-import org.opengis.filter.SpatialOperatorName;
+
+// Branch-dependent imports
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/Wrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/Wrapper.java
index d9d2b82..f9c5cb9 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/Wrapper.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/Wrapper.java
@@ -40,7 +40,7 @@
 import org.apache.sis.util.Debug;
 
 // Branch-dependent imports
-import org.opengis.filter.SpatialOperatorName;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java
index e6c0b50..d3eea5e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java
@@ -63,8 +63,8 @@
 import org.locationtech.jts.simplify.TopologyPreservingSimplifier;
 
 // Branch-dependent imports
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.DistanceOperatorName;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.internal.geoapi.filter.DistanceOperatorName;
 
 
 /**
@@ -706,7 +706,7 @@
              * We wrap that exception because `Geometry.transform(…)` does not declare `FactoryException`.
              * We may revisit in a future version if `Geometry.transform(…)` method declaration is updated.
              */
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionNames.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionNames.java
index cff9c0c..79db1f9 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionNames.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionNames.java
@@ -30,22 +30,10 @@
  * @module
  */
 public final class FunctionNames extends Static {
-    /** Value of {@link org.opengis.filter.NullOperator#getOperatorType()}. */
-    public static final String PROPERTY_IS_NULL = "PROPERTY_IS_NULL";
-
-    /** Value of {@link org.opengis.filter.NilOperator#getOperatorType()}. */
-    public static final String PROPERTY_IS_NIL = "PROPERTY_IS_NIL";
-
-    /** Value of {@link org.opengis.filter.LikeOperator#getOperatorType()}. */
-    public static final String PROPERTY_IS_LIKE = "PROPERTY_IS_LIKE";
-
-    /** Value of {@link org.opengis.filter.BetweenComparisonOperator#getOperatorType()}. */
-    public static final String PROPERTY_IS_BETWEEN = "PROPERTY_IS_BETWEEN";
-
-    /** Value of {@link org.opengis.filter.Literal#getFunctionName()}. */
+    /** Value of {@code Literal.getFunctionName()}. */
     public static final String Literal = "Literal";
 
-    /** Value of {@link org.opengis.filter.ValueReference#getFunctionName()}. */
+    /** Value of {@code ValueReference.getFunctionName()}. */
     public static final String ValueReference = "ValueReference";
 
     /** The "Add" (+) arithmetic expression. */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java
index d7abeb4..a6387c7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java
@@ -17,11 +17,11 @@
 package org.apache.sis.internal.filter;
 
 import java.util.Collection;
-import org.opengis.filter.Expression;
+import org.apache.sis.filter.Expression;
 
 
 /**
- * A factory of {@link org.opengis.filter} functions identified by their names.
+ * A factory of {@code org.opengis.filter} functions identified by their names.
  * Each factory can provide an arbitrary number of functions, enumerated by {@link #getNames()}.
  * The {@link org.apache.sis.filter.DefaultFilterFactory#function(String, Expression...)} method
  * delegates to this interface for creating the function implementation for a given name.
@@ -38,7 +38,7 @@
  *
  * @see org.opengis.filter.FilterFactory#function(String, Expression...)
  *
- * @todo Replace by {@link org.opengis.filter.capability.ExtendedCapabilities}.
+ * @todo Replace by {@code org.opengis.filter.capability.ExtendedCapabilities}.
  */
 public interface FunctionRegister {
     /**
@@ -60,7 +60,7 @@
     /**
      * Create a new function of the given name with given parameters.
      *
-     * @param  <R>         the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+     * @param  <R>         the type of resources (e.g. {@code Feature}) used as inputs.
      * @param  name        name of the function to create (not null).
      * @param  parameters  function parameters.
      * @return function for the given name and parameters.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
index 2d362d2..243b0e6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
@@ -32,8 +32,7 @@
 import org.apache.sis.filter.Optimization;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -44,7 +43,7 @@
  * @author  Alexis Manin (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the geometry implementation type.
  *
  * @see org.apache.sis.filter.ConvertFunction
@@ -139,7 +138,7 @@
      *
      * @param  input  the geometry to evaluate with this expression.
      * @return the geometry wrapper, or {@code null} if the evaluated value is null.
-     * @throws InvalidFilterValueException if the expression result is not an instance of a supported type.
+     * @throws IllegalArgumentException if the expression result is not an instance of a supported type.
      */
     @Override
     public GeometryWrapper<G> apply(final R input) {
@@ -152,7 +151,7 @@
         } else try {
             return library.castOrWrap(value);
         } catch (ClassCastException e) {
-            throw new InvalidFilterValueException(Errors.format(
+            throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalClass_2, library.rootClass, Classes.getClass(value)), e);
         }
         return library.toGeometry2D(envelope, WraparoundMethod.NONE);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java
index dfcf716..cd78942 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java
@@ -39,10 +39,8 @@
 import org.apache.sis.internal.system.Loggers;
 
 // Branch-dependent imports
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -86,9 +84,9 @@
      *
      * @see Expression#getFunctionName()
      */
-    protected static <T> AttributeType<T> createType(final Class<T> type, final Object name) {
+    protected static <T> DefaultAttributeType<T> createType(final Class<T> type, final Object name) {
         return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name),
-                                          type, 1, 1, null, (AttributeType<?>[]) null);
+                                          type, 1, 1, null, (DefaultAttributeType<?>[]) null);
     }
 
     /**
@@ -142,12 +140,12 @@
     /**
      * Returns an expression whose results is a geometry wrapper.
      *
-     * @param  <R>         the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+     * @param  <R>         the type of resources (e.g. {@code Feature}) used as inputs.
      * @param  <G>         the geometry implementation type.
      * @param  library     the geometry library to use.
      * @param  expression  the expression providing source values.
      * @return an expression whose results is a geometry wrapper.
-     * @throws InvalidFilterValueException if the given expression is already a wrapper
+     * @throws IllegalArgumentException if the given expression is already a wrapper
      *         but for another geometry implementation.
      */
     @SuppressWarnings("unchecked")
@@ -158,7 +156,7 @@
             if (library.equals(((GeometryConverter<?,?>) expression).library)) {
                 return (GeometryConverter<R,G>) expression;
             } else {
-                throw new InvalidFilterValueException();        // TODO: provide a message.
+                throw new IllegalArgumentException();        // TODO: provide a message.
             }
         }
         return new GeometryConverter<>(library, expression);
@@ -168,7 +166,7 @@
      * If the given exception was wrapped by {@link #toGeometryWrapper(Geometries, Expression)},
      * returns the original expression. Otherwise returns the given expression.
      *
-     * @param  <R>         the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+     * @param  <R>         the type of resources (e.g. {@code Feature}) used as inputs.
      * @param  <G>         the geometry implementation type.
      * @param  expression  the expression to unwrap.
      * @return the unwrapped expression.
@@ -194,7 +192,7 @@
         if (expression instanceof GeometryConverter<?,?>) {
             return ((GeometryConverter<?,G>) expression).library;
         }
-        throw new InvalidFilterValueException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
+        throw new IllegalArgumentException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/SortByComparator.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/SortByComparator.java
index 3727386..0309648 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/SortByComparator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/SortByComparator.java
@@ -26,9 +26,9 @@
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 
 // Branch-dependent imports
-import org.opengis.filter.SortBy;
-import org.opengis.filter.SortProperty;
-import org.opengis.filter.ValueReference;
+import org.apache.sis.internal.geoapi.filter.SortBy;
+import org.apache.sis.internal.geoapi.filter.SortProperty;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Visitor.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Visitor.java
index ceb3a32..8d57f2d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Visitor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/Visitor.java
@@ -21,18 +21,17 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.function.BiConsumer;
-import org.opengis.util.CodeList;
 import org.apache.sis.internal.feature.Resources;
 
 // Branch-dependent imports
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.DistanceOperatorName;
-import org.opengis.filter.TemporalOperatorName;
-import org.opengis.filter.ComparisonOperatorName;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.internal.geoapi.filter.LogicalOperator;
+import org.apache.sis.internal.geoapi.filter.LogicalOperatorName;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.internal.geoapi.filter.DistanceOperatorName;
+import org.apache.sis.internal.geoapi.filter.TemporalOperatorName;
+import org.apache.sis.internal.geoapi.filter.ComparisonOperatorName;
 
 
 /**
@@ -52,7 +51,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <A>  type of the accumulator object where actions will write their results.
  *
  * @since 1.1
@@ -63,9 +62,9 @@
      * All filters known to this visitor.
      * May contain an entry associated to the {@code null} key.
      *
-     * @see #setFilterHandler(CodeList, BiConsumer)
+     * @see #setFilterHandler(Enum, BiConsumer)
      */
-    private final Map<CodeList<?>, BiConsumer<Filter<R>,A>> filters;
+    private final Map<Enum<?>, BiConsumer<Filter<R>,A>> filters;
 
     /**
      * All expressions known to this visitor.
@@ -119,7 +118,7 @@
      * @param  type  identification of the filter type (can be {@code null}).
      * @return the action to execute when the identified filter is found, or {@code null} if none.
      */
-    protected final BiConsumer<Filter<R>,A> getFilterHandler(final CodeList<?> type) {
+    protected final BiConsumer<Filter<R>,A> getFilterHandler(final Enum<?> type) {
         return filters.get(type);
     }
 
@@ -143,7 +142,7 @@
      * @param  type    identification of the filter type (can be {@code null}).
      * @param  action  the action to execute when the identified filter is found.
      */
-    protected final void setFilterHandler(final CodeList<?> type, final BiConsumer<Filter<R>,A> action) {
+    protected final void setFilterHandler(final Enum<?> type, final BiConsumer<Filter<R>,A> action) {
         filters.put(type, action);
     }
 
@@ -154,8 +153,8 @@
      * @param  lastType  identification of the last filter type (inclusive).
      * @param  action    the action to execute when an identified filter is found.
      */
-    private void setFamilyHandlers(final CodeList<?> lastType, final BiConsumer<Filter<R>,A> action) {
-        for (final CodeList<?> type : lastType.family()) {
+    private void setFamilyHandlers(final Enum<?> lastType, final BiConsumer<Filter<R>,A> action) {
+        for (final Enum<?> type : lastType.getClass().getEnumConstants()) {
             filters.put(type, action);
             if (type == lastType) break;
         }
@@ -200,8 +199,8 @@
      * @param  action  the action to execute when one of the enumerated filters is found.
      */
     protected final void setNullAndNilHandlers(final BiConsumer<Filter<R>,A> action) {
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_NULL), action);
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_NIL),  action);
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_NULL, action);
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_NIL,  action);
     }
 
     /**
@@ -252,7 +251,7 @@
      *
      * @param  types  types of filters to remove.
      */
-    protected final void removeFilterHandlers(final Collection<? extends CodeList<?>> types) {
+    protected final void removeFilterHandlers(final Collection<? extends Enum<?>> types) {
         filters.keySet().removeAll(types);
     }
 
@@ -264,7 +263,7 @@
      * This method often needs to be invoked with instances of {@code Filter<? super R>},
      * because this is the type of filters returned by GeoAPI methods such as {@link LogicalOperator#getOperands()}.
      * But the parameterized type expected by this method matches the parameterized type of handlers registered by
-     * {@link #setFilterHandler(CodeList, BiConsumer)} and similar methods, which use the exact {@code <R>} type.
+     * {@link #setFilterHandler(Enum, BiConsumer)} and similar methods, which use the exact {@code <R>} type.
      * This restriction exists because when doing otherwise, parameterized types become hard to express in Java
      * (we get a cascade of {@code super} keywords, something like {@code <? super ? super R>}).
      * However doing the {@code (Filter<R>) filter} cast is actually safe if the handlers do not invoke any
@@ -279,7 +278,7 @@
      * @throws UnsupportedOperationException if there is no action registered for the given filter.
      */
     public void visit(final Filter<R> filter, final A accumulator) {
-        final CodeList<?> type = (filter != null) ? filter.getOperatorType() : null;
+        final Enum<?> type = (filter != null) ? filter.getOperatorType() : null;
         final BiConsumer<Filter<R>, A> f = filters.get(type);
         if (f != null) {
             f.accept(filter, accumulator);
@@ -329,7 +328,7 @@
      * @param  accumulator  where to write the result of all actions.
      * @throws UnsupportedOperationException if there is no default action.
      */
-    protected void typeNotFound(final CodeList<?> type, final Filter<R> filter, final A accumulator) {
+    protected void typeNotFound(final Enum<?> type, final Filter<R> filter, final A accumulator) {
         throw new UnsupportedOperationException(Resources.format(Resources.Keys.CanNotVisit_2, 0, type));
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java
index 08a167e..bdb6eb4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionWithSRID.java
@@ -31,10 +31,9 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.internal.geoapi.filter.Literal;
 
 
 /**
@@ -46,7 +45,7 @@
  * @author  Alexis Manin (Geomatys)
  * @version 1.3
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  *
  * @since 1.1
  * @module
@@ -118,7 +117,7 @@
                 if (literalCRS) try {
                     setTargetCRS(value);
                 } catch (FactoryException e) {
-                    throw new InvalidFilterValueException(e);
+                    throw new IllegalArgumentException(e);
                 }
             }
         } else {
@@ -215,7 +214,7 @@
      * @throws IllegalArgumentException if the given feature type does not contain the expected properties.
      */
     @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
+    public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
         final PropertyTypeBuilder pt = super.expectedType(valueType, addTo);
         if (pt instanceof AttributeTypeBuilder<?>) {
             // We must unconditionally override the CRS set by parent class.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java
index dac8fcd..4b8623f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryConstructor.java
@@ -26,8 +26,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -39,7 +38,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  *
  * @since 1.1
@@ -123,7 +122,7 @@
             final Object   geometry = result.implementation();
             final Class<?> expected = operation.getReturnType(library);
             if (!expected.isInstance(geometry)) {
-                throw new InvalidFilterValueException(Errors.format(
+                throw new IllegalArgumentException(Errors.format(
                         Errors.Keys.IllegalArgumentClass_3, "geom", expected, geometry.getClass()));
             }
             if (srid != null) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryParser.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryParser.java
index 1678f6f..d9db732 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryParser.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/GeometryParser.java
@@ -22,8 +22,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -35,7 +34,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  *
  * @since 1.1
@@ -101,7 +100,7 @@
                     case ST_BdPolyFromText:
                     case ST_BdMPolyFromWKB:
                     case ST_BdMPolyFromText: break;
-                    default: warning(new InvalidFilterValueException(Errors.format(
+                    default: warning(new IllegalArgumentException(Errors.format(
                                             Errors.Keys.IllegalArgumentClass_3, inputName(),
                                             getValueClass(),
                                             result.implementation().getClass())), true);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java
index d219252..558649a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/OneGeometry.java
@@ -23,7 +23,7 @@
 import org.apache.sis.internal.feature.GeometryWrapper;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -35,7 +35,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.3
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  *
  * @since 1.1
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/Registry.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/Registry.java
index dac812d..9202548 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/Registry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/Registry.java
@@ -23,7 +23,7 @@
 import org.apache.sis.internal.jdk9.JDK9;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
+import org.apache.sis.filter.Expression;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SQLMM.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SQLMM.java
index 49a2b8d..f6cf525 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SQLMM.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SQLMM.java
@@ -25,7 +25,7 @@
 import static org.apache.sis.internal.feature.GeometryType.*;
 
 // Branch-dependent imports
-import org.opengis.filter.SpatialOperatorName;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
 
 
 /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromBinary.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromBinary.java
index 301eda0..177fb9f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromBinary.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromBinary.java
@@ -21,7 +21,7 @@
 import org.apache.sis.internal.feature.GeometryWrapper;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -31,7 +31,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  *
  * @since 1.1
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromText.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromText.java
index 5f4772c..0d315a0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromText.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_FromText.java
@@ -20,7 +20,7 @@
 import org.apache.sis.internal.feature.GeometryWrapper;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -30,7 +30,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  *
  * @since 1.1
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java
index 56864f1..a6f1b86 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Point.java
@@ -27,8 +27,7 @@
 import static java.lang.Double.isNaN;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -47,7 +46,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  *
  * @since 1.1
@@ -109,13 +108,13 @@
     /**
      * Returns the numerical value evaluated by the expression at the given index.
      * If the value is {@code null}, then {@link Double#NaN} is returned.
-     * If the value is not a number, then an {@link InvalidFilterValueException} is thrown.
+     * If the value is not a number, then an {@link IllegalArgumentException} is thrown.
      *
      * @param  input  the object to be evaluated by the expression. Can be {@code null}.
      * @param  index  the parameter index.
      * @param  name   parameter name to report in exception message if the value is not a number.
      * @return the numerical value, or {@link Double#NaN} if the value was null.
-     * @throws InvalidFilterValueException if the value is not a number.
+     * @throws IllegalArgumentException if the value is not a number.
      */
     private double value(final R input, final int index, final String name) {
         final Object value = parameters[index].apply(input);
@@ -124,7 +123,7 @@
         } else if (value instanceof Number) {
             return ((Number) value).doubleValue();
         } else {
-            throw new InvalidFilterValueException(Errors.format(
+            throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalArgumentClass_3, name, Number.class, value.getClass()));
         }
     }
@@ -136,7 +135,7 @@
      *
      * @param  value  the WKB or WKT value to parse. Can be {@code null}.
      * @return the parsed point, or {@code null} if the given value is null.
-     * @throws InvalidFilterValueException if the value is not a string or byte array.
+     * @throws IllegalArgumentException if the value is not a string or byte array.
      * @throws Exception if parsing failed for another reason.
      */
     private GeometryWrapper<G> parse(final Object value) throws Exception {
@@ -150,7 +149,7 @@
         } else if (value instanceof String) {
             point = library.parseWKT((String) value);
         } else {
-            throw new InvalidFilterValueException(Errors.format(
+            throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalArgumentClass_3, "wkt|wkb", String.class, value.getClass()));
         }
         final Object implementation = point.implementation();
@@ -158,7 +157,7 @@
             return point;
         } else {
             final String type = (value instanceof String) ? "wkt" : "wkb";
-            throw new InvalidFilterValueException(Errors.format(
+            throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalArgumentClass_3, type, library.pointClass, point.getClass()));
         }
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java
index 6b3f83b..194e740 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Transform.java
@@ -26,9 +26,8 @@
 import org.apache.sis.util.collection.BackingStoreException;
 
 // Branch-dependent imports
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.InvalidFilterValueException;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.internal.geoapi.filter.Literal;
 
 
 /**
@@ -78,7 +77,7 @@
      * Creates a new function with the given parameters. It is caller's responsibility to ensure
      * that the given array is non-null and does not contain null elements.
      *
-     * @throws InvalidFilterValueException if CRS can not be constructed from the second expression.
+     * @throws IllegalArgumentException if CRS can not be constructed from the second expression.
      */
     ST_Transform(final Expression<? super R, ?>[] parameters, final Geometries<G> library) {
         super(SQLMM.ST_Transform, parameters, PRESENT);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
index d250e5c..b9af211 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
@@ -32,9 +32,8 @@
 import org.apache.sis.util.iso.Names;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -44,7 +43,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.2
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  *
  * @since 1.1
  * @module
@@ -202,12 +201,12 @@
      * @param  valueType  the type of features on which to apply this expression.
      * @param  addTo      where to add the type of properties evaluated by this expression.
      * @return builder of type resulting from expression evaluation (never null).
-     * @throws InvalidFilterValueException if the given feature type does not contain the expected properties,
+     * @throws IllegalArgumentException if the given feature type does not contain the expected properties,
      *         or if this method can not determine the result type of the expression.
      *         It may be because that expression is backed by an unsupported implementation.
      */
     @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
+    public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
         AttributeTypeBuilder<?> att;
 cases:  if (operation.isGeometryInOut()) {
             final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(getParameters().get(0));
@@ -222,7 +221,7 @@
                     }
                 }
             }
-            throw new InvalidFilterValueException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
+            throw new IllegalArgumentException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
         } else {
             att = addTo.addAttribute(getValueClass());
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java
index b457540..c556259 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/TwoGeometries.java
@@ -26,11 +26,10 @@
 import org.apache.sis.internal.feature.GeometryWrapper;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.internal.geoapi.filter.Literal;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
 
 
 /**
@@ -40,7 +39,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.3
  *
- * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
  * @param  <G>  the implementation type of geometry objects.
  *
  * @since 1.1
@@ -83,7 +82,7 @@
      */
     @Override
     public Expression<? super R, ?> optimize(final Optimization optimization) {
-        final FeatureType featureType = optimization.getFeatureType();
+        final DefaultFeatureType featureType = optimization.getFeatureType();
         if (featureType != null) {
             final Expression<? super R, ?> p1 = unwrap(geometry1);
             if (p1 instanceof ValueReference<?,?> && unwrap(geometry2) instanceof Literal<?,?>) try {
@@ -101,7 +100,7 @@
                         }
                     }
                 }
-            } catch (PropertyNotFoundException | TransformException e) {
+            } catch (IllegalArgumentException | TransformException e) {
                 warning(e, true);
             }
         }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/BetweenComparisonOperator.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/BetweenComparisonOperator.java
index 2984463..8e4e8f3 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/BetweenComparisonOperator.java
@@ -14,24 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.filter.Filter;
+
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public interface BetweenComparisonOperator<R> extends Filter<R> {
+    Expression<? super R, ?> getExpression();
+    Expression<? super R, ?> getLowerBoundary();
+    Expression<? super R, ?> getUpperBoundary();
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/BinaryComparisonOperator.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/BinaryComparisonOperator.java
index 2984463..b2fff0f 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/BinaryComparisonOperator.java
@@ -14,24 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.filter.Filter;
+
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public interface BinaryComparisonOperator<R> extends Filter<R> {
+    Expression<? super R, ?> getOperand1();
+    Expression<? super R, ?> getOperand2();
+    boolean isMatchingCase();
+    MatchAction getMatchAction();
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/ComparisonOperatorName.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/ComparisonOperatorName.java
index 2984463..cc7e751 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/ComparisonOperatorName.java
@@ -14,24 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public enum ComparisonOperatorName {
+    PROPERTY_IS_EQUAL_TO,
+    PROPERTY_IS_NOT_EQUAL_TO,
+    PROPERTY_IS_LESS_THAN,
+    PROPERTY_IS_GREATER_THAN,
+    PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,
+    PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO,
+    PROPERTY_IS_BETWEEN,
+    PROPERTY_IS_LIKE,
+    PROPERTY_IS_NULL,
+    PROPERTY_IS_NIL;
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/DistanceOperatorName.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/DistanceOperatorName.java
index 2984463..d5a2c5c 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/DistanceOperatorName.java
@@ -14,24 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public enum DistanceOperatorName {
+    BEYOND, WITHIN;
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/FilterExpressions.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/FilterExpressions.java
new file mode 100644
index 0000000..6bb1690
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/FilterExpressions.java
@@ -0,0 +1,105 @@
+/*
+ * 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.geoapi.filter;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.AbstractList;
+import java.util.Locale;
+import org.opengis.util.ScopedName;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.util.iso.Names;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+final class FilterExpressions<R> extends AbstractList<Expression<? super R, ?>> {
+    private final List<Filter<? super R>> filters;
+
+    FilterExpressions(final List<Filter<? super R>> filters) {
+        this.filters = Objects.requireNonNull(filters);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return filters.isEmpty();
+    }
+
+    @Override
+    public int size() {
+        return filters.size();
+    }
+
+    @Override
+    public Expression<? super R, ?> get(final int index) {
+        return new Element<>(filters.get(index));
+    }
+
+    private static final class Element<R> implements Expression<R,Boolean> {
+        private final Filter<R> filter;
+
+        Element(final Filter<R> filter) {
+            this.filter = Objects.requireNonNull(filter);
+        }
+
+        @Override
+        public ScopedName getFunctionName() {
+            final Enum<?> type = filter.getOperatorType();
+            final String identifier = type.name().toLowerCase(Locale.US);
+            if (identifier != null) {
+                return Names.createScopedName(Name.STANDARD, null, identifier);
+            } else {
+                return Names.createScopedName(Name.EXTENSION, null, type.name());
+            }
+        }
+
+        @Override
+        public List<Expression<? super R, ?>> getParameters() {
+            return filter.getExpressions();
+        }
+
+        @Override
+        public Boolean apply(final R input) {
+            return filter.test(input);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <N> Expression<R,N> toValueType(final Class<N> type) {
+            if (type.isAssignableFrom(Boolean.class)) return (Expression<R,N>) this;
+            else throw new ClassCastException();
+        }
+
+        @Override
+        public int hashCode() {
+            return ~filter.hashCode();
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            return (obj instanceof Element) && filter.equals(((Element) obj).filter);
+        }
+
+        @Override
+        public String toString() {
+            return "Expression[" + filter.toString() + ']';
+        }
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/Literal.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/Literal.java
new file mode 100644
index 0000000..85d0294
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/Literal.java
@@ -0,0 +1,41 @@
+/*
+ * 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.geoapi.filter;
+
+import java.util.List;
+import java.util.Collections;
+import org.opengis.util.ScopedName;
+import org.apache.sis.filter.Expression;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+public interface Literal<R,V> extends Expression<R,V> {
+    @Override
+    default ScopedName getFunctionName() {
+        return Name.LITERAL;
+    }
+
+    @Override
+    default List<Expression<? super R, ?>> getParameters() {
+        return Collections.emptyList();
+    }
+
+    V getValue();
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/LogicalOperator.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/LogicalOperator.java
new file mode 100644
index 0000000..8647cd1
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/LogicalOperator.java
@@ -0,0 +1,38 @@
+/*
+ * 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.geoapi.filter;
+
+import java.util.List;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+public interface LogicalOperator<R> extends Filter<R> {
+    @Override
+    LogicalOperatorName getOperatorType();
+
+    @Override
+    default List<Expression<? super R, ?>> getExpressions() {
+        return new FilterExpressions<>(getOperands());
+    }
+
+    List<Filter<? super R>> getOperands();
+}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/LogicalOperatorName.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/LogicalOperatorName.java
index 2984463..d9ddcbe 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/LogicalOperatorName.java
@@ -14,24 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public enum LogicalOperatorName {
+    AND, OR, NOT;
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/MatchAction.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/MatchAction.java
index 2984463..9ac098d 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/MatchAction.java
@@ -14,24 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public enum MatchAction {
+    ANY, ALL, ONE;
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/Name.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/Name.java
new file mode 100644
index 0000000..a4387f0
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/Name.java
@@ -0,0 +1,40 @@
+/*
+ * 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.geoapi.filter;
+
+import org.apache.sis.internal.filter.FunctionNames;
+import org.apache.sis.util.iso.Names;
+import org.opengis.util.LocalName;
+import org.opengis.util.ScopedName;
+
+
+/**
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
+ */
+public final class Name {
+    static final LocalName STANDARD = Names.createLocalName(null, null, "fes");
+
+    static final LocalName EXTENSION = Names.createLocalName(null, null, "extension");
+
+    public static final ScopedName LITERAL = Names.createScopedName(STANDARD, null, FunctionNames.Literal);
+
+    public static final ScopedName VALUE_REFERENCE = Names.createScopedName(STANDARD, null, FunctionNames.ValueReference);
+
+    private Name() {
+    }
+}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SortBy.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SortBy.java
index 2984463..960845f 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SortBy.java
@@ -14,24 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
+import java.util.List;
+import java.util.Comparator;
+
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public interface SortBy<R> extends Comparator<R> {
+    List<SortProperty<R>> getSortProperties();
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SortOrder.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SortOrder.java
index 2984463..dab0c48 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SortOrder.java
@@ -14,24 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
+public enum SortOrder {
+    ASCENDING("ASC"), DESCENDING("DESC");
 
+    private final String sql;
+
+    private SortOrder(final String sql) {
+        this.sql = sql;
+    }
+
+    public String toSQL() {
+        return sql;
+    }
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SortProperty.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SortProperty.java
index 2984463..ab27a0e 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SortProperty.java
@@ -14,24 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
+import java.util.Comparator;
+
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
+public interface SortProperty<R> extends Comparator<R> {
+    ValueReference<? super R, ?> getValueReference();
 
+    SortOrder getSortOrder();
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SpatialOperatorName.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SpatialOperatorName.java
index 2984463..9c5c4ec 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/SpatialOperatorName.java
@@ -14,24 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public enum SpatialOperatorName {
+    BBOX, EQUALS, DISJOINT, INTERSECTS, TOUCHES, CROSSES, WITHIN, CONTAINS, OVERLAPS;
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/TemporalOperatorName.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/TemporalOperatorName.java
index 2984463..0780b9e 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/TemporalOperatorName.java
@@ -14,24 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public enum TemporalOperatorName {
+    AFTER, BEFORE, BEGINS, BEGUN_BY, CONTAINS, DURING, EQUALS, OVERLAPS, MEETS, ENDS,
+    OVERLAPPED_BY, MET_BY, ENDED_BY, ANY_INTERACTS;
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/ValueReference.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/ValueReference.java
index 2984463..dc3b5ba 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/ValueReference.java
@@ -14,24 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.filter;
 
-import org.opengis.style.Symbolizer;
+import org.apache.sis.filter.Expression;
+
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public interface ValueReference<R,V> extends Expression<R,V> {
+    String getXPath();
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/package-info.java
similarity index 60%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/package-info.java
index 2984463..47fb2f8 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/geoapi/filter/package-info.java
@@ -14,24 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
-
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
+ * Placeholder for GeoAPI 3.1 interfaces (not yet released).
+ * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
-}
+package org.apache.sis.internal.geoapi.filter;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/image/Isolines.java b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/image/Isolines.java
index abc87cd..d8703f3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/image/Isolines.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/image/Isolines.java
@@ -27,13 +27,13 @@
 import java.awt.Shape;
 import java.awt.geom.Path2D;
 import java.awt.image.RenderedImage;
-import org.opengis.coverage.grid.SequenceType;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.image.PixelIterator;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.image.SequenceType;
 
 import static org.apache.sis.internal.processing.image.IsolineTracer.UPPER_LEFT;
 import static org.apache.sis.internal.processing.image.IsolineTracer.UPPER_RIGHT;
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
index 33d1abf..24d0f50 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
@@ -27,7 +27,6 @@
 import java.awt.image.WritableRenderedImage;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.coverage.PointOutsideCoverageException;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.datum.PixelInCell;
@@ -43,6 +42,9 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
+// Branch-dependent imports
+import org.apache.sis.coverage.PointOutsideCoverageException;
+
 import static org.apache.sis.test.FeatureAssert.*;
 
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index 843d330..fda4030 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -21,7 +21,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.metadata.spatial.DimensionNameType;
-import org.opengis.coverage.PointOutsideCoverageException;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.AxisDirection;
@@ -29,6 +28,7 @@
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.GeneralDirectPosition;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
+import org.apache.sis.coverage.PointOutsideCoverageException;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.Matrix3;
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
index 994cb2b..8793a3a 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
@@ -26,7 +26,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractFeatureTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractFeatureTest.java
index 592838a..3b0892a 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractFeatureTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractFeatureTest.java
@@ -23,12 +23,6 @@
 
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Tests some default method implementations provided in {@link AbstractFeature}.
@@ -68,9 +62,9 @@
          */
         CustomFeature(final DefaultFeatureType type) {
             super(type);
-            for (final PropertyType pt : type.getProperties(true)) {
-                if (pt instanceof AttributeType<?>) {
-                    Object value = ((AttributeType<?>) pt).getDefaultValue();
+            for (final AbstractIdentifiedType pt : type.getProperties(true)) {
+                if (pt instanceof DefaultAttributeType<?>) {
+                    Object value = ((DefaultAttributeType<?>) pt).getDefaultValue();
                     if (isMultiValued(pt)) {
                         value = new ArrayList<>(PropertyView.singletonOrEmpty(value));
                     }
@@ -84,8 +78,8 @@
         /**
          * Returns {@code true} if the given property can contains more than one value.
          */
-        private static boolean isMultiValued(final PropertyType pt) {
-            return (pt instanceof AttributeType<?>) && (((AttributeType<?>) pt).getMaximumOccurs() > 1);
+        private static boolean isMultiValued(final AbstractIdentifiedType pt) {
+            return (pt instanceof DefaultAttributeType<?>) && (((DefaultAttributeType<?>) pt).getMaximumOccurs() > 1);
         }
 
         /**
@@ -116,15 +110,15 @@
          */
         @Override
         public void setPropertyValue(final String name, Object value) {
-            final PropertyType type = getType().getProperty(name);
+            final AbstractIdentifiedType type = getType().getProperty(name);
             final boolean isMultiValued = isMultiValued(type);
             if (isMultiValued && !(value instanceof Collection<?>)) {
                 value = new ArrayList<>(PropertyView.singletonOrEmpty(value));
             }
             if (value != null) {
                 final Class<?> base;
-                if (type instanceof AttributeType<?>) {
-                    base = ((AttributeType<?>) type).getValueClass();
+                if (type instanceof DefaultAttributeType<?>) {
+                    base = ((DefaultAttributeType<?>) type).getValueClass();
                 } else {
                     base = FeatureType.class;
                 }
@@ -159,10 +153,7 @@
      */
     @Override
     boolean assertSameProperty(final String name, final Property expected, final boolean modified) {
-        final Property actual = feature.getProperty(name);
-        if ((expected instanceof PropertyView) == (actual instanceof PropertyView)) {
-            assertEquals(name, expected, actual);
-        }
+        final Property actual = (Property) feature.getProperty(name);
         assertSame("name", expected.getName(), actual.getName());
         if (!modified) {
             assertSame("value", expected.getValue(), actual.getValue());
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java
index 841275e..c40f504 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java
@@ -26,9 +26,6 @@
 
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-
 
 /**
  * Tests {@link CharacteristicMap} indirectly, through {@link AbstractAttribute} construction.
@@ -61,14 +58,14 @@
     }
 
     /**
-     * Tests adding explicitly a characteristic with {@link CharacteristicMap#put(String, Attribute)}.
+     * Tests adding explicitly a characteristic with {@code CharacteristicMap.put(String, Attribute)}.
      */
     @Test
     public void testPut() {
-        final AbstractAttribute<?>     temperature     = temperature();
-        final AbstractAttribute<?>     units           = create(temperature, "units");
-        final AbstractAttribute<?>     accuracy        = create(temperature, "accuracy");
-        final Map<String,Attribute<?>> characteristics = temperature.characteristics();
+        final AbstractAttribute<?> temperature = temperature();
+        final AbstractAttribute<?> units       = create(temperature, "units");
+        final AbstractAttribute<?> accuracy    = create(temperature, "accuracy");
+        final Map<String,AbstractAttribute<?>> characteristics = temperature.characteristics();
         /*
          * Verify that the map is initially empty.
          */
@@ -148,16 +145,16 @@
     }
 
     /**
-     * Tests adding a characteristic indirectly with {@link CharacteristicMap#addValue(Attribute)}.
+     * Tests adding a characteristic indirectly with {@code CharacteristicMap.addValue(Attribute)}.
      */
     @Test
     @DependsOnMethod("testPut")
     public void testAddValue() {
-        final AbstractAttribute<?>     temperature     = temperature();
-        final AbstractAttribute<?>     units           = create(temperature, "units");
-        final AbstractAttribute<?>     accuracy        = create(temperature, "accuracy");
-        final Map<String,Attribute<?>> characteristics = temperature.characteristics();
-        final Collection<Attribute<?>> values          = characteristics.values();
+        final AbstractAttribute<?> temperature = temperature();
+        final AbstractAttribute<?> units       = create(temperature, "units");
+        final AbstractAttribute<?> accuracy    = create(temperature, "accuracy");
+        final Map<String,AbstractAttribute<?>> characteristics = temperature.characteristics();
+        final Collection<AbstractAttribute<?>> values          = characteristics.values();
         /*
          * Verify that the collection is initially empty.
          */
@@ -211,10 +208,10 @@
     @Test
     @DependsOnMethod("testPut")
     public void testAddKey() {
-        final Attribute<?> units, accuracy;
-        final AbstractAttribute<?>     temperature     = temperature();
-        final Map<String,Attribute<?>> characteristics = temperature.characteristics();
-        final Collection<String>       keys            = characteristics.keySet();
+        final AbstractAttribute<?> units, accuracy;
+        final AbstractAttribute<?> temperature = temperature();
+        final Map<String,AbstractAttribute<?>> characteristics = temperature.characteristics();
+        final Collection<String> keys = characteristics.keySet();
         /*
          * Verify that the collection is initially empty.
          */
@@ -272,8 +269,8 @@
      * @param  accuracy         the second expected value in iteration order.
      * @param  characteristics  the map to verify.
      */
-    private static void assertEntriesEqual(final Attribute<?> units, final Attribute<?> accuracy,
-            final Map<String,Attribute<?>> characteristics)
+    private static void assertEntriesEqual(final AbstractAttribute<?> units, final AbstractAttribute<?> accuracy,
+            final Map<String,AbstractAttribute<?>> characteristics)
     {
         assertArrayEquals("keySet", new String[] {"accuracy", "units"}, characteristics.keySet().toArray());
         assertArrayEquals("values", new Object[] { accuracy ,  units }, characteristics.values().toArray());
@@ -292,7 +289,7 @@
      */
     private static void setAccuracy(final AbstractAttribute<Float> temperature, final boolean isFirstTime, final float value) {
         assertEquals("keySet.add", isFirstTime, temperature.characteristics().keySet().add("accuracy"));
-        final Attribute<Float> accuracy = Features.cast(temperature.characteristics().get("accuracy"), Float.class);
+        final AbstractAttribute<Float> accuracy = Features.cast(temperature.characteristics().get("accuracy"), Float.class);
         accuracy.setValue(value);
     }
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
index 86cb6d5..d7ae539 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
@@ -26,11 +26,8 @@
 import static org.apache.sis.feature.DefaultAssociationRole.NAME_KEY;
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
 import org.apache.sis.util.iso.Names;
 
-
 /**
  * Tests {@link CharacteristicTypeMap} indirectly, through {@link DefaultAttributeType} construction.
  *
@@ -75,9 +72,9 @@
      */
     @Test
     public void testMapMethods() {
-        final AttributeType<?> units, accuracy;
+        final DefaultAttributeType<?> units, accuracy;
         final DefaultAttributeType<Float> temperature = temperature();
-        final Map<String, AttributeType<?>> characteristics = temperature.characteristics();
+        final Map<String, DefaultAttributeType<?>> characteristics = temperature.characteristics();
 
         assertFalse  ("isEmpty",        characteristics.isEmpty());
         assertEquals ("size", 2,        characteristics.size());
@@ -122,7 +119,7 @@
         a3 = new DefaultAttributeType<>(singletonMap(NAME_KEY, Names.parseGenericName(null, null, "ns2:s3:units")), String.class, 1, 1, "°C");
         tp = new DefaultAttributeType<>(singletonMap(NAME_KEY, "temperature"), Float.class, 1, 1, null, a1, a2, a3);
 
-        final Map<String, AttributeType<?>> characteristics = tp.characteristics();
+        final Map<String, DefaultAttributeType<?>> characteristics = tp.characteristics();
         assertSame("ns1:accuracy", a1, characteristics.get("ns1:accuracy"));
         assertSame("ns2:accuracy", a2, characteristics.get("ns2:accuracy"));
         assertSame("ns2:s3:units", a3, characteristics.get("ns2:s3:units"));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java b/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java
index e03b7c0..ac8fa16 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java
@@ -25,9 +25,6 @@
 
 import static java.util.Collections.singleton;
 
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
-
 
 /**
  * For testing {@link AbstractAttribute} customization.
@@ -53,7 +50,7 @@
     /**
      * Creates a new attribute.
      */
-    public CustomAttribute(final AttributeType<V> type) {
+    public CustomAttribute(final DefaultAttributeType<V> type) {
         super(type);
         value = type.getDefaultValue();
     }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
index 67d15d1..ab4c8a1 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
@@ -19,7 +19,6 @@
 import java.util.Map;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
-import org.opengis.feature.FeatureAssociationRole;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
@@ -31,9 +30,6 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.FeatureType;
-
 
 /**
  * Tests {@link DefaultAssociationRole}.
@@ -74,7 +70,7 @@
      */
     static DefaultFeatureType twinTownCity(final boolean cyclic) {
         final DefaultAssociationRole twinTown = twinTown(cyclic);
-        final FeatureType parent = cyclic ? DefaultFeatureTypeTest.city() : twinTown.getValueType();
+        final DefaultFeatureType parent = cyclic ? DefaultFeatureTypeTest.city() : twinTown.getValueType();
         return createType("Twin town", parent, twinTown);
     }
 
@@ -87,10 +83,10 @@
      * @return the feature type to use for testing purpose.
      */
     private static DefaultFeatureType createType(final Object name,
-            final FeatureType parent, final FeatureAssociationRole... property)
+            final DefaultFeatureType parent, final DefaultAssociationRole... property)
     {
         return new DefaultFeatureType(singletonMap(NAME_KEY, name),
-                false, new FeatureType[] {parent}, property);
+                false, new DefaultFeatureType[] {parent}, property);
     }
 
     /**
@@ -103,7 +99,7 @@
     }
 
     /**
-     * Tests {@link DefaultAssociationRole#getTitleProperty(FeatureAssociationRole)}.
+     * Tests {@code DefaultAssociationRole.getTitleProperty(FeatureAssociationRole)}.
      */
     @Test
     public void testGetTitleProperty() {
@@ -126,7 +122,7 @@
     @Test
     public void testBidirectionalAssociation() {
         final DefaultFeatureType twinTown = twinTownCity(true);
-        final FeatureAssociationRole association = (FeatureAssociationRole) twinTown.getProperty("twin town");
+        final DefaultAssociationRole association = (DefaultAssociationRole) twinTown.getProperty("twin town");
         assertSame("twinTown.property(“twin town”).valueType", twinTown, association.getValueType());
         /*
          * Creates a FeatureType copy containing the same properties. Used for verifying
@@ -141,7 +137,7 @@
     }
 
     /**
-     * Tests {@link DefaultFeatureType#isAssignableFrom(FeatureType)} and {@link DefaultFeatureType#equals(Object)}
+     * Tests {@code DefaultFeatureType.isAssignableFrom(FeatureType)} and {@code DefaultFeatureType.equals(Object)}
      * on a feature type having a bidirectional association to an other feature. This test will fall in an infinite
      * loop if the implementation does not have proper guard against infinite recursivity.
      */
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java
index 141118f..0cba383 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java
@@ -32,10 +32,6 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static java.util.Collections.singletonMap;
 
-// Branch-dependent imports
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Tests {@link DefaultFeatureType}.
@@ -205,7 +201,7 @@
             final String... expected)
     {
         int index = 0;
-        for (final PropertyType property : feature.getProperties(includeSuperTypes)) {
+        for (final AbstractIdentifiedType property : feature.getProperties(includeSuperTypes)) {
             assertTrue("Found more properties than expected.", index < expected.length);
             final String name = expected[index++];
             assertNotNull(name, property);
@@ -276,8 +272,8 @@
                 name("Festival"), false, null, city, population, festival);
 
         assertUnmodifiable(complex);
-        final Collection<PropertyType> properties = complex.getProperties(false);
-        final Iterator<PropertyType> it = properties.iterator();
+        final Collection<AbstractIdentifiedType> properties = complex.getProperties(false);
+        final Iterator<AbstractIdentifiedType> it = properties.iterator();
 
         assertEquals("name",            "Festival",                     complex.getName().toString());
         assertTrue  ("superTypes",                                      complex.getSuperTypes().isEmpty());
@@ -337,7 +333,7 @@
         final DefaultFeatureType feature = new DefaultFeatureType(
                 name("City"), false, null, city, cityId, population);
 
-        final Iterator<PropertyType> it = feature.getProperties(false).iterator();
+        final Iterator<AbstractIdentifiedType> it = feature.getProperties(false).iterator();
         assertSame ("properties[0]", city,       it.next());
         assertSame ("properties[1]", cityId,     it.next());
         assertSame ("properties[2]", population, it.next());
@@ -403,7 +399,7 @@
          * Try to add an operation that depends on a non-existent property.
          * Such construction shall not be allowed.
          */
-        final PropertyType parliament = new LinkOperation(identifierName, DefaultAttributeTypeTest.parliament());
+        final AbstractIdentifiedType parliament = new LinkOperation(identifierName, DefaultAttributeTypeTest.parliament());
         try {
             final DefaultFeatureType illegal = new DefaultFeatureType(featureName, false, parent, parliament);
             fail("Should not have been allowed to create this feature:\n" + illegal);
@@ -471,7 +467,7 @@
         assertPropertiesEquals(metroCapital, false, "country");
         assertPropertiesEquals(metroCapital, true, "city", "population", "region", "isGlobal", "parliament", "country");
         assertEquals("property(“region”).valueClass", CharSequence.class,
-                ((AttributeType<?>) metroCapital.getProperty("region")).getValueClass());
+                ((DefaultAttributeType<?>) metroCapital.getProperty("region")).getValueClass());
 
         // Check based only on name.
         assertTrue ("maybeAssignableFrom", DefaultFeatureType.maybeAssignableFrom(capital, metroCapital));
@@ -515,7 +511,7 @@
         assertPropertiesEquals(worldMetropolis, false, "region", "temperature");
         assertPropertiesEquals(worldMetropolis, true, "city", "population", "region", "isGlobal", "universities", "temperature");
         assertEquals("property(“region”).valueClass", InternationalString.class,
-                ((AttributeType<?>) worldMetropolis.getProperty("region")).getValueClass());
+                ((DefaultAttributeType<?>) worldMetropolis.getProperty("region")).getValueClass());
 
         // Check based only on name.
         assertTrue ("maybeAssignableFrom", DefaultFeatureType.maybeAssignableFrom(metropolis, worldMetropolis));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java
index cecab78..53379a1 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java
@@ -36,11 +36,6 @@
 
 import static org.apache.sis.test.ReferencingAssert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-
 
 /**
  * Tests {@link EnvelopeOperation}.
@@ -64,12 +59,12 @@
      * @see #initialize()
      * @see #initialize(CoordinateReferenceSystem, boolean, CoordinateReferenceSystem, boolean)
      */
-    private FeatureType type;
+    private DefaultFeatureType type;
 
     /**
      * The feature created by a test method. Saved for allowing additional checks or operations.
      */
-    private Feature feature;
+    private AbstractFeature feature;
 
     /**
      * Creates the feature type with two geometric properties without default CRS.
@@ -159,9 +154,9 @@
 
         if (asCharacteristic) {
             @SuppressWarnings("unchecked")
-            final Attribute<GeometryWrapper<?>> property =
-                    (Attribute<GeometryWrapper<?>>) feature.getProperty(propertyName);
-            final Attribute<CoordinateReferenceSystem> crsCharacteristic = Features.cast(
+            final AbstractAttribute<GeometryWrapper<?>> property =
+                    (AbstractAttribute<GeometryWrapper<?>>) feature.getProperty(propertyName);
+            final AbstractAttribute<CoordinateReferenceSystem> crsCharacteristic = Features.cast(
                     property.getType().characteristics().get(AttributeConvention.CRS),
                     CoordinateReferenceSystem.class).newInstance();
             crsCharacteristic.setValue(crs);
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
index 091b18e..dff7027 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
@@ -30,9 +30,6 @@
 
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent import
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Tests {@link FeatureFormat}.
@@ -86,7 +83,7 @@
     @SuppressWarnings("serial")
     public void testFeatureTypeWithOperations() {
         DefaultFeatureType feature = DefaultFeatureTypeTest.city();
-        final PropertyType city = feature.getProperty("city");
+        final AbstractIdentifiedType city = feature.getProperty("city");
         feature = new DefaultFeatureType(name("Identified city"), false, new DefaultFeatureType[] {feature},
                 FeatureOperations.link(name("someId"), city),
                 FeatureOperations.compound(name("anotherId"), ":", "<", ">", city, feature.getProperty("population")),
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureMemoryBenchmark.java b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureMemoryBenchmark.java
index d613565..e420655 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureMemoryBenchmark.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureMemoryBenchmark.java
@@ -20,7 +20,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Random;
-import org.opengis.feature.Feature;
 import org.apache.sis.internal.util.StandardDateFormat;
 
 import static java.util.Collections.singletonMap;
@@ -126,7 +125,7 @@
         final Float  latitude  = random.nextFloat() * 180 -  90;
         final Float  longitude = random.nextFloat() * 360 - 180;
         if (type != null) {
-            final Feature feature = type.newInstance();
+            final AbstractFeature feature = type.newInstance();
             feature.setPropertyValue("city",      city);
             feature.setPropertyValue("latitude",  latitude);
             feature.setPropertyValue("longitude", longitude);
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureOperationsTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureOperationsTest.java
index 3b99b5b..62bff0d 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureOperationsTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureOperationsTest.java
@@ -36,9 +36,6 @@
 
 import static org.apache.sis.test.ReferencingAssert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Tests a feature combining various {@link FeatureOperations} applied of either sparse or dense features.
@@ -76,7 +73,7 @@
         final DefaultAttributeType<?> normalizedCRS = new DefaultAttributeType<>(
                 name(AttributeConvention.CRS_CHARACTERISTIC), CoordinateReferenceSystem.class, 1, 1, HardCodedCRS.WGS84);
 
-        final PropertyType[] attributes = {
+        final AbstractIdentifiedType[] attributes = {
             new DefaultAttributeType<>(name("name"),          String.class,  1, 1, null),
             new DefaultAttributeType<>(name("classes"),       Polygon.class, 1, 1, null, standardCRS),
             new DefaultAttributeType<>(name("climbing wall"), Point.class,   1, 1, null, standardCRS),
@@ -106,7 +103,7 @@
      */
     @Test
     public void testConstruction() throws FactoryException {
-        final PropertyType property = school(3).getProperty("bounds");
+        final AbstractIdentifiedType property = school(3).getProperty("bounds");
         assertInstanceOf("bounds", EnvelopeOperation.class, property);
         final EnvelopeOperation op = (EnvelopeOperation) property;
         assertSame("targetCRS", HardCodedCRS.WGS84, op.targetCRS);
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
index 149443b..3ef2671 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
@@ -32,11 +32,6 @@
 
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Property;
-
 
 /**
  * Tests common to {@link DenseFeatureTest} and {@link SparseFeatureTest}.
@@ -77,7 +72,7 @@
 
     /**
      * Asserts that {@link AbstractFeature#getProperty(String)} returns the given instance.
-     * This assertion is verified after a call to {@link AbstractFeature#setProperty(Property)}
+     * This assertion is verified after a call to {@code AbstractFeature.setProperty(Property)}
      * and should be true for all Apache SIS concrete implementations. But it is not guaranteed
      * to be true for non-SIS implementations, for example built on top of {@code AbstractFeature}
      * without overriding {@code setProperty(Property)}.
@@ -106,7 +101,7 @@
              *   - Attribute value shall be the same than the one we got at the beginning of this method.
              *   - Attribute values (as a collection) is either empty or contains the same value.
              */
-            final Attribute<?> property = (Attribute<?>) feature.getProperty(name);
+            final AbstractAttribute<?> property = (AbstractAttribute<?>) feature.getProperty(name);
             assertSame(name, feature.getType().getProperty(name), property.getType());
             assertSame(name, value, property.getValue());
             final Collection<?> values = property.getValues();
@@ -162,9 +157,9 @@
         feature.setPropertyValue("REF_INSEE",   "92007");
         feature.setPropertyValue("CODE_POSTAL", "92220");
 
-        assertEquals("CODE_POSTAL", "92220",   feature.getProperty("CODE_POSTAL").getValue());
-        assertEquals("REF_INSEE",   "92007",   feature.getProperty("REF_INSEE")  .getValue());
-        assertEquals("COMMUNE",     "Bagneux", feature.getProperty("COMMUNE")    .getValue());
+        assertEquals("CODE_POSTAL", "92220",   ((AbstractAttribute) feature.getProperty("CODE_POSTAL")).getValue());
+        assertEquals("REF_INSEE",   "92007",   ((AbstractAttribute) feature.getProperty("REF_INSEE"))  .getValue());
+        assertEquals("COMMUNE",     "Bagneux", ((AbstractAttribute) feature.getProperty("COMMUNE"))    .getValue());
 
         assertEquals("CODE_POSTAL", "92220",   feature.getPropertyValue("CODE_POSTAL"));
         assertEquals("REF_INSEE",   "92007",   feature.getPropertyValue("REF_INSEE"));
@@ -301,7 +296,7 @@
     }
 
     /**
-     * Tests the possibility to plugin custom attributes via {@link AbstractFeature#setProperty(Property)}.
+     * Tests the possibility to plugin custom attributes via {@code AbstractFeature.setProperty(Property)}.
      */
     @Test
     @DependsOnMethod({"testSimpleValues", "testSimpleProperties"})
@@ -309,7 +304,7 @@
         feature = createFeature(DefaultFeatureTypeTest.city());
         final AbstractAttribute<String> wrong = SingletonAttributeTest.parliament();
         final CustomAttribute<String> city = new CustomAttribute<>(Features.cast(
-                (AttributeType<?>) feature.getType().getProperty("city"), String.class));
+                (DefaultAttributeType<?>) feature.getType().getProperty("city"), String.class));
 
         feature.setProperty(city);
         setAttributeValue("city", "Utopia", "Atlantide");
@@ -438,13 +433,13 @@
          * Force the conversion of a property value into a full Property object on one and only one of
          * the Features to be compared. The implementation shall be able to wrap or unwrap the values.
          */
-        assertEquals("Tokyo", clone.getProperty("city").getValue());
+        assertEquals("Tokyo", ((AbstractAttribute) clone.getProperty("city")).getValue());
         assertEquals("hashCode", feature.hashCode(), clone.hashCode());
         assertEquals("equals", feature, clone);
         /*
          * For the other Feature instance to contain full Property object and test again.
          */
-        assertEquals("Tokyo", feature.getProperty("city").getValue());
+        assertEquals("Tokyo", ((AbstractAttribute) feature.getProperty("city")).getValue());
         assertEquals("hashCode", feature.hashCode(), clone.hashCode());
         assertEquals("equals", feature, clone);
     }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java
index 73972bb..73b4721 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java
@@ -22,10 +22,6 @@
 
 import static org.junit.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * Tests {@link Features}.
@@ -39,7 +35,7 @@
 @DependsOn(SingletonAttributeTest.class)
 public final strictfp class FeaturesTest extends TestCase {
     /**
-     * Tests {@link Features#cast(AttributeType, Class)}.
+     * Tests {@code Features.cast(AttributeType, Class)}.
      */
     @Test
     public void testCastAttributeType() {
@@ -57,7 +53,7 @@
     }
 
     /**
-     * Tests {@link Features#cast(Attribute, Class)}.
+     * Tests {@code Features.cast(Attribute, Class)}.
      */
     @Test
     public void testCastAttributeInstance() {
@@ -75,17 +71,17 @@
     }
 
     /**
-     * Tests {@link Features#validate(Feature)}.
+     * Tests {@code Features.validate(Feature)}.
      */
     @Test
     public void testValidate() {
-        final Feature feature = DefaultFeatureTypeTest.city().newInstance();
+        final AbstractFeature feature = DefaultFeatureTypeTest.city().newInstance();
 
         // Should not pass validation.
         try {
             Features.validate(feature);
             fail("Feature is invalid because of missing property “population”. Validation should have raised an exception.");
-        } catch (InvalidPropertyValueException ex) {
+        } catch (IllegalArgumentException ex) {
             String message = ex.getMessage();
             assertTrue(message, message.contains("city") || message.contains("population"));
         }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/NoOperation.java b/core/sis-feature/src/test/java/org/apache/sis/feature/NoOperation.java
index f680a4f..86870d9 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/NoOperation.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/NoOperation.java
@@ -20,11 +20,6 @@
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptorGroup;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Property;
-
 
 /**
  * An operation that does nothing.
@@ -45,7 +40,7 @@
     /**
      * The type of the result, or {@code null} if none.
      */
-    private final IdentifiedType result;
+    private final AbstractIdentifiedType result;
 
     /**
      * Constructs an operation from the given properties. The identification map is given unchanged to
@@ -56,7 +51,7 @@
      * @param  result          the type of the result, or {@code null} if none.
      */
     NoOperation(final Map<String,?> identification,
-            final ParameterDescriptorGroup parameters, final IdentifiedType result)
+            final ParameterDescriptorGroup parameters, final AbstractIdentifiedType result)
     {
         super(identification);
         this.parameters = parameters;
@@ -79,7 +74,7 @@
      * @return the type of the result, or {@code null} if none.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return result;
     }
 
@@ -89,7 +84,7 @@
      * @return {@code null}
      */
     @Override
-    public Property apply(Feature feature, ParameterValueGroup parameters) {
+    public Object apply(AbstractFeature feature, ParameterValueGroup parameters) {
         return null;
     }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
index 179584b..89e4c05 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
@@ -23,10 +23,6 @@
 import static java.util.Collections.singletonMap;
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Tests {@link SingletonAssociation}.
@@ -48,7 +44,7 @@
      * and Le Mans, France in 836.” — source: Wikipedia</blockquote>
      */
     static AbstractAssociation twinTown() {
-        final Feature twinTown = DefaultFeatureTypeTest.city().newInstance();
+        final AbstractFeature twinTown = DefaultFeatureTypeTest.city().newInstance();
         twinTown.setPropertyValue("city", "Le Mans");
         twinTown.setPropertyValue("population", 143240); // In 2011.
         final AbstractAssociation association = new SingletonAssociation(DefaultAssociationRoleTest.twinTown(false));
@@ -61,9 +57,9 @@
      */
     @Test
     public void testWrongValue() {
-        final AbstractAssociation association  = twinTown();
-        final PropertyType population   = association.getRole().getValueType().getProperty("population");
-        final Feature      otherFeature = new DefaultFeatureType(
+        final AbstractAssociation    association  = twinTown();
+        final AbstractIdentifiedType population   = association.getRole().getValueType().getProperty("population");
+        final AbstractFeature        otherFeature = new DefaultFeatureType(
                 singletonMap(DefaultFeatureType.NAME_KEY, "Population"), false, null, population).newInstance();
         try {
             association.setValue(otherFeature);
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/StringJoinOperationTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/StringJoinOperationTest.java
index 453c728..aa94159 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/StringJoinOperationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/StringJoinOperationTest.java
@@ -26,12 +26,6 @@
 
 import static org.junit.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * Tests {@link StringJoinOperation}.
@@ -61,9 +55,9 @@
      * @return the feature for a person.
      */
     private static DefaultFeatureType person() {
-        final PropertyType nameType = new DefaultAttributeType<>(name("name"), String.class, 1, 1, null);
-        final PropertyType ageType  = new DefaultAttributeType<>(name("age"), Integer.class, 1, 1, null);
-        final PropertyType cmpType  = FeatureOperations.compound(name("concat"), "/", "<<:", ":>>", nameType, ageType);
+        final AbstractIdentifiedType nameType = new DefaultAttributeType<>(name("name"), String.class, 1, 1, null);
+        final AbstractIdentifiedType ageType  = new DefaultAttributeType<>(name("age"), Integer.class, 1, 1, null);
+        final AbstractIdentifiedType cmpType  = FeatureOperations.compound(name("concat"), "/", "<<:", ":>>", nameType, ageType);
         return new DefaultFeatureType(name("person"), false, null, nameType, ageType, cmpType);
     }
 
@@ -146,7 +140,7 @@
         try {
             feature.setPropertyValue("concat", "((:marc/21:>>");
             fail("Should fail because of mismatched prefix.");
-        } catch (InvalidPropertyValueException e) {
+        } catch (IllegalArgumentException e) {
             String message = e.getMessage();
             assertTrue(message, message.contains("<<:"));
             assertTrue(message, message.contains("(("));
@@ -154,7 +148,7 @@
         try {
             feature.setPropertyValue("concat", "<<:marc/21:))");
             fail("Should fail because of mismatched suffix.");
-        } catch (InvalidPropertyValueException e) {
+        } catch (IllegalArgumentException e) {
             String message = e.getMessage();
             assertTrue(message, message.contains(":>>"));
             assertTrue(message, message.contains("))"));
@@ -162,14 +156,14 @@
         try {
             feature.setPropertyValue("concat", "<<:marc/21/julie:>>");
             fail("Should fail because of too many components.");
-        } catch (InvalidPropertyValueException e) {
+        } catch (IllegalArgumentException e) {
             String message = e.getMessage();
             assertTrue(message, message.contains("<<:marc/21/julie:>>"));
         }
         try {
             feature.setPropertyValue("concat", "<<:marc/julie:>>");
             fail("Should fail because of unparsable number.");
-        } catch (InvalidPropertyValueException e) {
+        } catch (IllegalArgumentException e) {
             String message = e.getMessage();
             assertTrue(message, message.contains("julie"));
             assertTrue(message, message.contains("age"));
@@ -182,12 +176,12 @@
      */
     @Test
     public void testFeatureAssociation() {
-        final PropertyType id1 = new DefaultAttributeType<>(name(AttributeConvention.IDENTIFIER_PROPERTY), String.class, 1, 1, null);
-        final FeatureType  ft1 = new DefaultFeatureType(name("Child feature"), false, null, id1);
-        final PropertyType  p1 = new DefaultAssociationRole(name("first"), ft1, 1, 1);
-        final PropertyType  p2 = new DefaultAttributeType<>(name("second"), Integer.class, 1, 1, null);
-        final PropertyType idc = FeatureOperations.compound(name("concat"), "/", "<<:", ":>>", p1, p2);
-        final Feature  feature = new DefaultFeatureType(name("Parent feature"), false, null, p1, p2, idc).newInstance();
+        final AbstractIdentifiedType id1 = new DefaultAttributeType<>(name(AttributeConvention.IDENTIFIER_PROPERTY), String.class, 1, 1, null);
+        final DefaultFeatureType     ft1 = new DefaultFeatureType(name("Child feature"), false, null, id1);
+        final AbstractIdentifiedType  p1 = new DefaultAssociationRole(name("first"), ft1, 1, 1);
+        final AbstractIdentifiedType  p2 = new DefaultAttributeType<>(name("second"), Integer.class, 1, 1, null);
+        final AbstractIdentifiedType idc = FeatureOperations.compound(name("concat"), "/", "<<:", ":>>", p1, p2);
+        final AbstractFeature    feature = new DefaultFeatureType(name("Parent feature"), false, null, p1, p2, idc).newInstance();
         /*
          * For empty feature, should have only the prefix, delimiter and suffix.
          */
@@ -201,7 +195,7 @@
          * Create the associated feature and set its identifier.
          * The compound identifier shall be updated accordingly.
          */
-        final Feature f1 = ft1.newInstance();
+        final AbstractFeature f1 = ft1.newInstance();
         feature.setPropertyValue("first", f1);
         f1.setPropertyValue("sis:identifier", "SomeKey");
         assertEquals("<<:SomeKey/21:>>", feature.getPropertyValue("concat"));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java
index 605af1b..23d07e5 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java
@@ -24,7 +24,7 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureAssociationRole;
+import org.apache.sis.feature.DefaultAssociationRole;
 
 
 /**
@@ -50,7 +50,7 @@
                 .setMaximumOccurs(2)
                 .setMinimumOccurs(1);
 
-        final FeatureAssociationRole role = builder.build();
+        final DefaultAssociationRole role = builder.build();
         assertEquals("minimumOccurs", 1, role.getMinimumOccurs());
         assertEquals("maximumOccurs", 2, role.getMaximumOccurs());
         assertEquals("designation", new SimpleInternationalString("A designation"),          role.getDesignation());
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
index e9cf8b5..0e2bea6 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
@@ -32,12 +32,11 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -62,7 +61,7 @@
         assertEquals("default name", "string", builder.getName().toString());
 
         builder.setName("myScope", "myName");
-        final AttributeType<?> att = builder.build();
+        final DefaultAttributeType<?> att = builder.build();
 
         assertEquals("name", "myScope:myName",   att.getName().toString());
         assertEquals("valueClass", String.class, att.getValueClass());
@@ -88,7 +87,7 @@
         assertSame(builder, builder.setDefaultValue("test default value."));
         assertSame(builder, builder.setMinimumOccurs(10).setMaximumOccurs(60));
         assertSame(builder, builder.setMaximalLength(80));
-        final AttributeType<?> att = builder.build();
+        final DefaultAttributeType<?> att = builder.build();
 
         assertEquals("name",          "myScope:myName",      att.getName().toString());
         assertEquals("definition",    "test definition",     att.getDefinition().toString());
@@ -148,7 +147,7 @@
         /*
          * Verify the attribute created by the builder.
          */
-        final AttributeType<?> att = newb.build();
+        final DefaultAttributeType<?> att = newb.build();
         assertEquals("name",          "temperature",      att.getName().toString());
         assertEquals("definition",    "test definition",  att.getDefinition().toString());
         assertEquals("description",   "test description", att.getDescription().toString());
@@ -258,19 +257,19 @@
         final AttributeTypeBuilder<Integer> boxBuilder = ftb.addAttribute(int.class).setName("boxed");
         assertEquals("Attribute value type should have been boxed", Integer.class, boxBuilder.getValueClass());
 
-        final FeatureType ft = ftb.build();
-        final PropertyType boxedProperty = ft.getProperty("boxed");
-        assertInstanceOf("Unexpected property type.", AttributeType.class, boxedProperty);
-        assertEquals("Attribute value type should have been boxed", Integer.class, ((AttributeType<?>) boxedProperty).getValueClass());
-        final Feature feature = ft.newInstance();
+        final DefaultFeatureType ft = ftb.build();
+        final AbstractIdentifiedType boxedProperty = ft.getProperty("boxed");
+        assertInstanceOf("Unexpected property type.", DefaultAttributeType.class, boxedProperty);
+        assertEquals("Attribute value type should have been boxed", Integer.class, ((DefaultAttributeType<?>) boxedProperty).getValueClass());
+        final AbstractFeature feature = ft.newInstance();
 
-        final Property p = feature.getProperty("boxed");
-        assertInstanceOf("Unexpected property type.", Attribute.class, p);
-        assertEquals("Attribute value type should have been boxed", Integer.class, ((Attribute<?>) p).getType().getValueClass());
+        final Object p = feature.getProperty("boxed");
+        assertInstanceOf("Unexpected property type.", AbstractAttribute.class, p);
+        assertEquals("Attribute value type should have been boxed", Integer.class, ((AbstractAttribute<?>) p).getType().getValueClass());
 
         int value = 3;
-        Features.cast((Attribute<?>) p, Integer.class).setValue(value);
-        assertEquals(value, p.getValue());
+        Features.cast((AbstractAttribute<?>) p, Integer.class).setValue(value);
+        assertEquals(value, ((AbstractAttribute) p).getValue());
 
         feature.setPropertyValue("boxed", Integer.valueOf(4));
         assertEquals(4, feature.getPropertyValue("boxed"));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java
index 792cd97..b386dbe 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java
@@ -23,7 +23,7 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -79,7 +79,7 @@
         /*
          * Verify the characteristic created by the builder.
          */
-        final AttributeType<?> att = newb.build();
+        final DefaultAttributeType<?> att = newb.build();
         assertEquals("name",          "stddev",           att.getName().toString());
         assertEquals("definition",    "test definition",  att.getDefinition().toString());
         assertEquals("description",   "test description", att.getDescription().toString());
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
index 32aeda4..3226c00 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
@@ -35,11 +35,9 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.Operation;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -55,13 +53,13 @@
 @DependsOn(AttributeTypeBuilderTest.class)
 public final strictfp class FeatureTypeBuilderTest extends TestCase {
     /**
-     * Verifies that {@link FeatureTypeBuilder#setSuperTypes(FeatureType...)} ignores null parents.
+     * Verifies that {@code FeatureTypeBuilder.setSuperTypes(FeatureType...)} ignores null parents.
      * This method tests only the builder state without creating feature type.
      */
     @Test
     public void testNullParents() {
         final FeatureTypeBuilder builder = new FeatureTypeBuilder(null);
-        assertSame(builder, builder.setSuperTypes(new FeatureType[6]));
+        assertSame(builder, builder.setSuperTypes(new DefaultFeatureType[6]));
         assertEquals(0, builder.getSuperTypes().length);
     }
 
@@ -115,7 +113,7 @@
             assertTrue(message, message.contains("name"));
         }
         assertSame(builder, builder.setName("scope", "test"));
-        final FeatureType type = builder.build();
+        final DefaultFeatureType type = builder.build();
 
         assertEquals("name", "scope:test",   type.getName().toString());
         assertFalse ("isAbstract",           type.isAbstract());
@@ -140,18 +138,18 @@
         builder.addAttribute(Point  .class).setName("location").setCRS(HardCodedCRS.WGS84);
         builder.addAttribute(Double .class).setName("score").setDefaultValue(10.0).setMinimumOccurs(5).setMaximumOccurs(50);
 
-        final FeatureType type = builder.build();
+        final DefaultFeatureType type = builder.build();
         assertEquals("name",        "myScope:myName",   type.getName().toString());
         assertEquals("definition",  "test definition",  type.getDefinition().toString());
         assertEquals("description", "test description", type.getDescription().toString());
         assertEquals("designation", "test designation", type.getDesignation().toString());
         assertTrue  ("isAbstract",                      type.isAbstract());
 
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        final AttributeType<?> a0 = (AttributeType<?>) it.next();
-        final AttributeType<?> a1 = (AttributeType<?>) it.next();
-        final AttributeType<?> a2 = (AttributeType<?>) it.next();
-        final AttributeType<?> a3 = (AttributeType<?>) it.next();
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        final DefaultAttributeType<?> a0 = (DefaultAttributeType<?>) it.next();
+        final DefaultAttributeType<?> a1 = (DefaultAttributeType<?>) it.next();
+        final DefaultAttributeType<?> a2 = (DefaultAttributeType<?>) it.next();
+        final DefaultAttributeType<?> a3 = (DefaultAttributeType<?>) it.next();
         assertFalse("properties count", it.hasNext());
 
         assertEquals("name", "name",     a0.getName().toString());
@@ -201,16 +199,16 @@
                 .setCRS(HardCodedCRS.WGS84)
                 .addRole(AttributeRole.DEFAULT_GEOMETRY);
 
-        final FeatureType type = builder.build();
+        final DefaultFeatureType type = builder.build();
         assertEquals("name", "scope:test", type.getName().toString());
         assertFalse ("isAbstract", type.isAbstract());
 
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        final PropertyType a0 = it.next();
-        final PropertyType a1 = it.next();
-        final PropertyType a2 = it.next();
-        final PropertyType a3 = it.next();
-        final PropertyType a4 = it.next();
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        final AbstractIdentifiedType a0 = it.next();
+        final AbstractIdentifiedType a1 = it.next();
+        final AbstractIdentifiedType a2 = it.next();
+        final AbstractIdentifiedType a3 = it.next();
+        final AbstractIdentifiedType a4 = it.next();
         assertFalse("properties count", it.hasNext());
 
         assertEquals("name", AttributeConvention.IDENTIFIER_PROPERTY, a0.getName());
@@ -232,15 +230,15 @@
         assertSame(builder, builder.setName("City"));
         builder.addAttribute(String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY).addRole(AttributeRole.IDENTIFIER_COMPONENT);
         builder.addAttribute(Integer.class).setName("population");
-        final FeatureType type = builder.build();
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        final PropertyType a0 = it.next();
-        final PropertyType a1 = it.next();
+        final DefaultFeatureType type = builder.build();
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        final AbstractIdentifiedType a0 = it.next();
+        final AbstractIdentifiedType a1 = it.next();
         assertFalse("properties count", it.hasNext());
         assertEquals("name", AttributeConvention.IDENTIFIER_PROPERTY, a0.getName());
-        assertEquals("type", String.class,  ((AttributeType<?>) a0).getValueClass());
+        assertEquals("type", String.class,  ((DefaultAttributeType<?>) a0).getValueClass());
         assertEquals("name", "population", a1.getName().toString());
-        assertEquals("type", Integer.class, ((AttributeType<?>) a1).getValueClass());
+        assertEquals("type", Integer.class, ((DefaultAttributeType<?>) a1).getValueClass());
     }
 
     /**
@@ -255,17 +253,17 @@
         assertSame(builder, builder.setName("City"));
         builder.addAttribute(Point.class).setName(AttributeConvention.GEOMETRY_PROPERTY).addRole(AttributeRole.DEFAULT_GEOMETRY);
         builder.addAttribute(Integer.class).setName("population");
-        final FeatureType type = builder.build();
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        final PropertyType a0 = it.next();
-        final PropertyType a1 = it.next();
-        final PropertyType a2 = it.next();
+        final DefaultFeatureType type = builder.build();
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        final AbstractIdentifiedType a0 = it.next();
+        final AbstractIdentifiedType a1 = it.next();
+        final AbstractIdentifiedType a2 = it.next();
         assertFalse("properties count", it.hasNext());
         assertEquals("name", AttributeConvention.ENVELOPE_PROPERTY, a0.getName());
         assertEquals("name", AttributeConvention.GEOMETRY_PROPERTY, a1.getName());
-        assertEquals("type", Point.class,   ((AttributeType<?>) a1).getValueClass());
+        assertEquals("type", Point.class,   ((DefaultAttributeType<?>) a1).getValueClass());
         assertEquals("name", "population", a2.getName().toString());
-        assertEquals("type", Integer.class, ((AttributeType<?>) a2).getValueClass());
+        assertEquals("type", Integer.class, ((DefaultAttributeType<?>) a2).getValueClass());
     }
 
     /**
@@ -297,7 +295,7 @@
         builder.addAttribute(Integer .class).setName("population");
         builder.addAttribute(Geometry.class).setName("area").roles().add(AttributeRole.DEFAULT_GEOMETRY);
 
-        final FeatureType type = builder.build();
+        final DefaultFeatureType type = builder.build();
         builder = new FeatureTypeBuilder(type);
         assertEquals("name", "City", builder.getName().toString());
         assertEquals("superTypes", 0, builder.getSuperTypes().length);
@@ -328,8 +326,8 @@
     @DependsOnMethod("testAddAttribute")
     public void testBuildCache() {
         final FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("City");
-        final AttributeType<String> name = builder.addAttribute(String.class).setName("name").build();
-        final FeatureType city = builder.build();
+        final DefaultAttributeType<String> name = builder.addAttribute(String.class).setName("name").build();
+        final DefaultFeatureType city = builder.build();
         assertSame("Should return the existing AttributeType.", name, city.getProperty("name"));
         assertSame("Should return the existing FeatureType.", city, builder.build());
 
@@ -351,13 +349,13 @@
     public void testEnvelopeOverride() {
         FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("CoverageRecord").setAbstract(true);
         builder.addAttribute(Geometry.class).setName(AttributeConvention.GEOMETRY_PROPERTY).addRole(AttributeRole.DEFAULT_GEOMETRY);
-        final FeatureType parentType = builder.build();
+        final DefaultFeatureType parentType = builder.build();
 
         builder = new FeatureTypeBuilder().setName("Record").setSuperTypes(parentType);
         builder.addAttribute(Envelope.class).setName(AttributeConvention.ENVELOPE_PROPERTY);
-        final FeatureType childType = builder.build();
+        final DefaultFeatureType childType = builder.build();
 
-        final Iterator<? extends PropertyType> it = childType.getProperties(true).iterator();
+        final Iterator<? extends AbstractIdentifiedType> it = childType.getProperties(true).iterator();
         assertPropertyEquals("sis:envelope", Envelope.class, it.next());
         assertPropertyEquals("sis:geometry", Geometry.class, it.next());
         assertFalse(it.hasNext());
@@ -370,15 +368,15 @@
     @Test
     public void testOverrideByOperation() {
         FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("Parent").setAbstract(true);
-        final AttributeType<Integer> pa = builder.addAttribute(Integer.class).setName("A").build();
+        final DefaultAttributeType<Integer> pa = builder.addAttribute(Integer.class).setName("A").build();
         builder.addAttribute(Integer.class).setName("B");
-        final FeatureType parentType = builder.build();
+        final DefaultFeatureType parentType = builder.build();
 
         builder = new FeatureTypeBuilder().setName("Child").setSuperTypes(parentType);
         builder.addProperty(FeatureOperations.link(Collections.singletonMap(AbstractOperation.NAME_KEY, "B"), pa));
-        final FeatureType childType = builder.build();
+        final DefaultFeatureType childType = builder.build();
 
-        final Iterator<? extends PropertyType> it = childType.getProperties(true).iterator();
+        final Iterator<? extends AbstractIdentifiedType> it = childType.getProperties(true).iterator();
         assertPropertyEquals("A", Integer.class, it.next());
         assertPropertyEquals("B", Integer.class, it.next());
         assertFalse(it.hasNext());
@@ -387,11 +385,11 @@
     /**
      * Verifies that the given property is an attribute with the given name and value class.
      */
-    private static void assertPropertyEquals(final String name, final Class<?> valueClass, IdentifiedType property) {
+    private static void assertPropertyEquals(final String name, final Class<?> valueClass, AbstractIdentifiedType property) {
         assertEquals("name", name, property.getName().toString());
-        if (property instanceof Operation) {
-            property = ((Operation) property).getResult();
+        if (property instanceof AbstractOperation) {
+            property = ((AbstractOperation) property).getResult();
         }
-        assertEquals("valueClass", valueClass, ((AttributeType<?>) property).getValueClass());
+        assertEquals("valueClass", valueClass, ((DefaultAttributeType<?>) property).getValueClass());
     }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/ArithmeticFunctionTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/ArithmeticFunctionTest.java
index 966cbef..9d0f8b9 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/ArithmeticFunctionTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/ArithmeticFunctionTest.java
@@ -22,9 +22,7 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.FilterFactory;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -39,7 +37,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Creates a new test case.
@@ -53,7 +51,7 @@
      */
     @Test
     public void testAdd() {
-        Expression<Feature,?> op = factory.add(factory.literal(10.0), factory.literal(20.0));
+        Expression<AbstractFeature,?> op = factory.add(factory.literal(10.0), factory.literal(20.0));
         assertEquals(30.0, op.apply(null));
         assertSerializedEquals(op);
     }
@@ -63,7 +61,7 @@
      */
     @Test
     public void testSubtract() {
-        Expression<Feature,?> op = factory.subtract(factory.literal(10.0), factory.literal(20.0));
+        Expression<AbstractFeature,?> op = factory.subtract(factory.literal(10.0), factory.literal(20.0));
         assertEquals(-10.0, op.apply(null));
         assertSerializedEquals(op);
     }
@@ -73,7 +71,7 @@
      */
     @Test
     public void testMultiply() {
-        Expression<Feature,?> op = factory.multiply(factory.literal(10.0), factory.literal(20.0));
+        Expression<AbstractFeature,?> op = factory.multiply(factory.literal(10.0), factory.literal(20.0));
         assertEquals(200.0, op.apply(null));
         assertSerializedEquals(op);
     }
@@ -83,7 +81,7 @@
      */
     @Test
     public void testDivide() {
-        Expression<Feature,?> op = factory.divide(factory.literal(10.0), factory.literal(20.0));
+        Expression<AbstractFeature,?> op = factory.divide(factory.literal(10.0), factory.literal(20.0));
         assertEquals(0.5, op.apply(null));
         assertSerializedEquals(op);
     }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java b/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
index 5879bc6..e208867 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
@@ -36,12 +36,8 @@
 import static org.apache.sis.test.Assert.assertSerializedEquals;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.DistanceOperator;
-import org.opengis.filter.BinarySpatialOperator;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.internal.geoapi.filter.Literal;
 
 
 /**
@@ -62,7 +58,7 @@
     /**
      * The factory to use for testing purpose.
      */
-    private final FilterFactory<Feature, G, Object> factory;
+    private final DefaultFilterFactory<AbstractFeature, G, Object> factory;
 
     /**
      * The geometry library used by this factory.
@@ -91,7 +87,7 @@
     /**
      * Creates the polygon identified by the given enumeration value.
      */
-    private Literal<Feature,G> literal(final Polygon p) {
+    private Literal<AbstractFeature,G> literal(final Polygon p) {
         final byte[] coordinates;
         boolean polygon = true;
         switch (p) {
@@ -104,7 +100,8 @@
             case TOUCHES:    coordinates = new byte[] {4,  2,    7,  5,    9,  3};  polygon = false; break;
             default: throw new AssertionError(p);
         }
-        return factory.literal(library.createPolyline(polygon, 2, Vector.create(coordinates, false)));
+        return (Literal<AbstractFeature,G>)
+                factory.literal(library.createPolyline(polygon, 2, Vector.create(coordinates, false)));
     }
 
     /**
@@ -112,10 +109,10 @@
      */
     @Test
     public void testBBOX() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
 
         double x=1, y=1;
-        BinarySpatialOperator<Feature> bbox = factory.bbox(right, new Envelope2D(null, x, y, 6-x, 6-y));
+        Filter<AbstractFeature> bbox = factory.bbox(right, new Envelope2D(null, x, y, 6-x, 6-y));
         assertTrue(bbox.test(null));
 
         x = -3; y = -2;
@@ -129,11 +126,10 @@
      */
     @Test
     public void bbox_preserve_expression_type() {
-        final BinarySpatialOperator<Feature> bbox = factory.bbox(literal(Polygon.RIGHT), new Envelope2D(null, 0, 0, 1, 1));
-        final Expression<? super Feature, ?> arg2 = bbox.getOperand2();
-        assertSame("The two ways to acquire the second argument return different values.", arg2, bbox.getExpressions().get(1));
+        final Filter<AbstractFeature> bbox = factory.bbox(literal(Polygon.RIGHT), new Envelope2D(null, 0, 0, 1, 1));
+        final Expression<? super AbstractFeature, ?> arg2 = bbox.getExpressions().get(1);
         assertInstanceOf("Second argument value should be an envelope.", Envelope.class,
-                         ((Literal<? super Feature, ?>) arg2).getValue());
+                         ((Literal<? super AbstractFeature, ?>) arg2).getValue());
     }
 
     /**
@@ -141,10 +137,10 @@
      */
     @Test
     public void testBeyond() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
         final Length distance = Quantities.create(1.5, Units.METRE);
 
-        DistanceOperator<Feature> beyond = factory.beyond(right, literal(Polygon.DISTANCE_1), distance);
+        Filter<AbstractFeature> beyond = factory.beyond(right, literal(Polygon.DISTANCE_1), distance);
         assertFalse(beyond.test(null));
 
         beyond = factory.beyond(right, literal(Polygon.DISTANCE_3), distance);
@@ -156,9 +152,9 @@
      */
     @Test
     public void testContains() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
 
-        BinarySpatialOperator<Feature> contains = factory.contains(literal(Polygon.CONTAINS), right);
+        Filter<AbstractFeature> contains = factory.contains(literal(Polygon.CONTAINS), right);
         assertTrue(contains.test(null));
 
         contains = factory.contains(literal(Polygon.DISTANCE_1), right);
@@ -170,9 +166,9 @@
      */
     @Test
     public void testCrosses() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
 
-        BinarySpatialOperator<Feature> crosses = factory.crosses(literal(Polygon.CONTAINS), right);
+        Filter<AbstractFeature> crosses = factory.crosses(literal(Polygon.CONTAINS), right);
         assertFalse(crosses.test(null));
 
         crosses = factory.crosses(literal(Polygon.CROSSES), right);
@@ -187,10 +183,10 @@
      */
     @Test
     public void testDWithin() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
         final Length distance = Quantities.create(1.5, Units.METRE);
 
-        DistanceOperator<Feature> within = factory.within(right, literal(Polygon.DISTANCE_1), distance);
+        Filter<AbstractFeature> within = factory.within(right, literal(Polygon.DISTANCE_1), distance);
         assertTrue(within.test(null));
 
         within = factory.within(right, literal(Polygon.DISTANCE_3), distance);
@@ -202,9 +198,9 @@
      */
     @Test
     public void testDisjoint() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
 
-        BinarySpatialOperator<Feature> disjoint = factory.disjoint(literal(Polygon.CONTAINS), right);
+        Filter<AbstractFeature> disjoint = factory.disjoint(literal(Polygon.CONTAINS), right);
         assertFalse(disjoint.test(null));
 
         disjoint = factory.disjoint(literal(Polygon.CROSSES), right);
@@ -219,9 +215,9 @@
      */
     @Test
     public void testEquals() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
 
-        BinarySpatialOperator<Feature> equal = factory.equals(literal(Polygon.CONTAINS), right);
+        Filter<AbstractFeature> equal = factory.equals(literal(Polygon.CONTAINS), right);
         assertFalse(equal.test(null));
 
         equal = factory.equals(literal(Polygon.CROSSES), right);
@@ -236,9 +232,9 @@
      */
     @Test
     public void testIntersect() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
 
-        BinarySpatialOperator<Feature> intersect = factory.intersects(literal(Polygon.CONTAINS), right);
+        Filter<AbstractFeature> intersect = factory.intersects(literal(Polygon.CONTAINS), right);
         assertTrue(intersect.test(null));
 
         intersect = factory.intersects(literal(Polygon.CROSSES), right);
@@ -259,9 +255,9 @@
      */
     @Test
     public void testOverlaps() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
 
-        BinarySpatialOperator<Feature> overlaps = factory.overlaps(literal(Polygon.CONTAINS), right);
+        Filter<AbstractFeature> overlaps = factory.overlaps(literal(Polygon.CONTAINS), right);
         assertFalse(overlaps.test(null));
 
         overlaps = factory.overlaps(literal(Polygon.DISTANCE_1), right);
@@ -279,9 +275,9 @@
      */
     @Test
     public void testTouches() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
 
-        BinarySpatialOperator<Feature> touches = factory.touches(literal(Polygon.CONTAINS), right);
+        Filter<AbstractFeature> touches = factory.touches(literal(Polygon.CONTAINS), right);
         assertFalse(touches.test(null));
 
         touches = factory.touches(literal(Polygon.CROSSES), right);
@@ -299,9 +295,9 @@
      */
     @Test
     public void testWithin() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
 
-        BinarySpatialOperator<Feature> within = factory.within(literal(Polygon.CONTAINS), right);
+        Filter<AbstractFeature> within = factory.within(literal(Polygon.CONTAINS), right);
         assertFalse(within.test(null));
 
         within = factory.within(literal(Polygon.CROSSES), right);
@@ -324,12 +320,12 @@
     @Test
     public void testWithReprojection() {
         final CoordinateReferenceSystem crs = HardCodedConversions.ESRI();
-        final Literal<Feature,G> geom = literal(Polygon.TOUCHES);
+        final Literal<AbstractFeature,G> geom = literal(Polygon.TOUCHES);
         library.castOrWrap(geom.getValue()).setCoordinateReferenceSystem(crs);
 
         // Initial verification without reprojection.
         Envelope2D envelope = new Envelope2D(crs, 0, 0, 10, 10);
-        BinarySpatialOperator<Feature> filter = factory.bbox(geom, envelope);
+        Filter<AbstractFeature> filter = factory.bbox(geom, envelope);
         assertTrue(filter.test(null));
 
         // Ensure no error is raised, even if a reprojection is involved.
@@ -343,8 +339,8 @@
      */
     @Test
     public void testSerialization() {
-        final Literal<Feature,G> right = literal(Polygon.RIGHT);
-        BinarySpatialOperator<Feature> overlaps = factory.overlaps(literal(Polygon.CONTAINS), right);
+        final Literal<AbstractFeature,G> right = literal(Polygon.RIGHT);
+        Filter<AbstractFeature> overlaps = factory.overlaps(literal(Polygon.CONTAINS), right);
         assertSerializedEquals(overlaps);
     }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/CapabilitiesTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/CapabilitiesTest.java
deleted file mode 100644
index b7501dd..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/CapabilitiesTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.filter;
-
-import java.util.Set;
-import org.opengis.util.LocalName;
-import org.apache.sis.test.TestCase;
-import org.apache.sis.test.TestUtilities;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-// Branch-dependent imports
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.capability.IdCapabilities;
-import org.opengis.filter.capability.ScalarCapabilities;
-
-
-/**
- * Tests {@link Capabilities} implementations.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class CapabilitiesTest extends TestCase {
-    /**
-     * Creates a new test case.
-     */
-    public CapabilitiesTest() {
-    }
-
-    /**
-     * Tests {@link Capabilities#getResourceIdentifiers()}.
-     */
-    @Test
-    public void testResourceIdentifiers() {
-        assertTrue(Capabilities.INSTANCE.getConformance().implementsResourceld());
-        final IdCapabilities idc = Capabilities.INSTANCE.getIdCapabilities().get();
-        final LocalName id = TestUtilities.getSingleton(idc.getResourceIdentifiers());
-        assertEquals("identifier", id.toString());
-    }
-
-    /**
-     * Tests {@link Capabilities#getComparisonOperators()}.
-     */
-    @Test
-    public void testComparisonOperators() {
-        final ScalarCapabilities c = Capabilities.INSTANCE.getScalarCapabilities().get();
-        final Set<ComparisonOperatorName> op = c.getComparisonOperators();
-        assertTrue(op.contains(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO));
-        assertTrue(op.contains(ComparisonOperatorName.PROPERTY_IS_LESS_THAN));
-        assertTrue(op.contains(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN));
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/ComparisonFilterTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/ComparisonFilterTest.java
index 0be250d..2895e99 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/ComparisonFilterTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/ComparisonFilterTest.java
@@ -18,18 +18,13 @@
 
 import org.junit.Test;
 import org.apache.sis.test.TestCase;
-import org.apache.sis.internal.filter.FunctionNames;
 
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Literal;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.ComparisonOperator;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.BetweenComparisonOperator;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.internal.geoapi.filter.ComparisonOperatorName;
+import org.apache.sis.internal.geoapi.filter.BetweenComparisonOperator;
 
 
 /**
@@ -45,12 +40,12 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Expressions used as constant for the tests.
      */
-    private final Literal<Feature,Integer> c05, c10, c20;
+    private final Expression<AbstractFeature,Integer> c05, c10, c20;
 
     /**
      * Expected name of the filter to be evaluated. The {@code evaluate(…)} methods
@@ -61,7 +56,7 @@
     /**
      * The filter tested by last call to {@code evaluate(…)} methods.
      */
-    private ComparisonOperator<Feature> filter;
+    private Filter<AbstractFeature> filter;
 
     /**
      * Creates a new test case.
@@ -77,7 +72,7 @@
      * Evaluates the given filter. The {@link #expectedName} field must be set before this method is invoked.
      * This method assumes that the first expression of all filters is {@link #c10}.
      */
-    private boolean evaluate(final BinaryComparisonOperator<Feature> filter) {
+    private boolean evaluate(final Filter<AbstractFeature> filter) {
         this.filter = filter;
         assertInstanceOf("Expected SIS implementation.", ComparisonFilter.class, filter);
         assertEquals("operatorType", expectedName, filter.getOperatorType());
@@ -88,7 +83,7 @@
     /**
      * Evaluates the given "Property is between" filter.
      */
-    private boolean evaluate(final BetweenComparisonOperator<Feature> filter) {
+    private boolean evaluate(final BetweenComparisonOperator<AbstractFeature> filter) {
         this.filter = filter;
         assertInstanceOf("Expected SIS implementation.", ComparisonFilter.Between.class, filter);
         assertEquals("operatorType", expectedName, filter.getOperatorType());
@@ -172,10 +167,10 @@
      */
     @Test
     public void testBetween() {
-        expectedName = ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN);
-        assertTrue (evaluate(factory.between(c10, c05, c20)));
-        assertFalse(evaluate(factory.between(c20, c05, c10)));
-        assertFalse(evaluate(factory.between(c05, c10, c20)));
+        expectedName = ComparisonOperatorName.PROPERTY_IS_BETWEEN;
+        assertTrue (evaluate((BetweenComparisonOperator<AbstractFeature>) factory.between(c10, c05, c20)));
+        assertFalse(evaluate((BetweenComparisonOperator<AbstractFeature>) factory.between(c20, c05, c10)));
+        assertFalse(evaluate((BetweenComparisonOperator<AbstractFeature>) factory.between(c05, c10, c20)));
         assertSerializedEquals(filter);
     }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/IdentifierFilterTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/IdentifierFilterTest.java
index 8387f00..daadf85 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/IdentifierFilterTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/IdentifierFilterTest.java
@@ -24,10 +24,8 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -43,7 +41,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Creates a new test case.
@@ -72,16 +70,16 @@
     public void testEvaluate() {
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.addAttribute(String.class).setName("att").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        final Feature f1 = ftb.setName("Test 1").build().newInstance();
+        final AbstractFeature f1 = ftb.setName("Test 1").build().newInstance();
         f1.setPropertyValue("att", "123");
 
         ftb.clear().addAttribute(Integer.class).setName("att").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        final Feature f2 = ftb.setName("Test 2").build().newInstance();
+        final AbstractFeature f2 = ftb.setName("Test 2").build().newInstance();
         f2.setPropertyValue("att", 123);
 
-        final Feature f3 = ftb.clear().setName("Test 3").build().newInstance();
+        final AbstractFeature f3 = ftb.clear().setName("Test 3").build().newInstance();
 
-        final Filter<Feature> id = factory.resourceId("123");
+        final Filter<AbstractFeature> id = factory.resourceId("123");
         assertTrue (id.test(f1));
         assertTrue (id.test(f2));
         assertFalse(id.test(f3));
@@ -94,13 +92,13 @@
     public void testEvaluateCombined() {
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.addAttribute(String.class).setName("att").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        final FeatureType type = ftb.setName("Test").build();
+        final DefaultFeatureType type = ftb.setName("Test").build();
 
-        final Feature f1 = type.newInstance(); f1.setPropertyValue("att", "123");
-        final Feature f2 = type.newInstance(); f2.setPropertyValue("att", "abc");
-        final Feature f3 = type.newInstance(); f3.setPropertyValue("att", "abc123");
+        final AbstractFeature f1 = type.newInstance(); f1.setPropertyValue("att", "123");
+        final AbstractFeature f2 = type.newInstance(); f2.setPropertyValue("att", "abc");
+        final AbstractFeature f3 = type.newInstance(); f3.setPropertyValue("att", "abc123");
 
-        final Filter<Feature> id = factory.or(
+        final Filter<AbstractFeature> id = factory.or(
                 factory.resourceId("abc"),
                 factory.resourceId("123"));
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java
index 7c89590..f8ee04f 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java
@@ -23,10 +23,9 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Literal;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.ValueReference;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.internal.geoapi.filter.Literal;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
 
 
 /**
@@ -41,7 +40,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Creates a new test case.
@@ -55,8 +54,8 @@
      */
     @Test
     public void testReferenceSerialization() {
-        final ValueReference<Feature, String> filter = factory.property("some_property", String.class);
-        assertEquals("some_property", filter.getXPath());
+        final Expression<AbstractFeature, String> filter = factory.property("some_property", String.class);
+        assertEquals("some_property", ((ValueReference<?,?>) filter).getXPath());
         assertSerializedEquals(filter);
     }
 
@@ -65,11 +64,11 @@
      */
     @Test
     public void testLiteralSerialization() {
-        final Literal<?,?> f1 = factory.literal(true);
-        final Literal<?,?> f2 = factory.literal("a text string");
-        final Literal<?,?> f3 = factory.literal('x');
-        final Literal<?,?> f4 = factory.literal(122);
-        final Literal<?,?> f5 = factory.literal(45.56);
+        final Literal<?,?> f1 = (Literal<?,?>) factory.literal(true);
+        final Literal<?,?> f2 = (Literal<?,?>) factory.literal("a text string");
+        final Literal<?,?> f3 = (Literal<?,?>) factory.literal('x');
+        final Literal<?,?> f4 = (Literal<?,?>) factory.literal(122);
+        final Literal<?,?> f5 = (Literal<?,?>) factory.literal(45.56);
 
         assertEquals(Boolean.TRUE,    f1.getValue());
         assertEquals("a text string", f2.getValue());
@@ -91,9 +90,9 @@
     public void testReferenceEvaluation() {
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.addAttribute(String.class).setName("some_property");
-        final Feature f = ftb.setName("Test").build().newInstance();
+        final AbstractFeature f = ftb.setName("Test").build().newInstance();
 
-        ValueReference<Feature,?> ref = factory.property("some_property");
+        Expression<AbstractFeature,?> ref = factory.property("some_property");
         assertNull(ref.apply(f));
         assertNull(ref.apply(null));
 
@@ -115,7 +114,7 @@
      */
     @Test
     public void testLiteralEvaluation() {
-        final Literal<Feature,?> literal = factory.literal(12.45);
+        final Expression<AbstractFeature,?> literal = factory.literal(12.45);
         assertEquals(12.45, literal.apply(null));
     }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/LikeFilterTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/LikeFilterTest.java
index 1ebe6d4..37784f9 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/LikeFilterTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/LikeFilterTest.java
@@ -22,9 +22,7 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.Literal;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -64,8 +62,8 @@
      */
     @Test
     public void testLike() {
-        final FilterFactory<Feature,Object,?> factory = DefaultFilterFactory.forFeatures();
-        final Literal<Feature, String> literal = factory.literal("Apache SIS");
+        final DefaultFilterFactory<AbstractFeature,Object,?> factory = DefaultFilterFactory.forFeatures();
+        final Expression<AbstractFeature, String> literal = factory.literal("Apache SIS");
 
         assertTrue (factory.like(literal, "Apache%").test(null));
         assertFalse(factory.like(literal, "Oracle%").test(null));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java
index 8dd829b..673d920 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java
@@ -28,13 +28,9 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Literal;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.LogicalOperator;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.internal.geoapi.filter.LogicalOperator;
 
 
 /**
@@ -50,7 +46,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Creates a new test case.
@@ -83,9 +79,9 @@
      */
     @Test
     public void testNot() {
-        final Literal<Feature,String>  literal = factory.literal("text");
-        final Filter<Feature>          operand = factory.isNull(literal);
-        final LogicalOperator<Feature> filter  = factory.not(operand);
+        final Expression<AbstractFeature,String> literal = factory.literal("text");
+        final Filter<AbstractFeature> operand = factory.isNull(literal);
+        final LogicalOperator<AbstractFeature> filter  = (LogicalOperator<AbstractFeature> ) factory.not(operand);
         assertArrayEquals(new Filter<?>[] {operand}, filter.getOperands().toArray());
         assertTrue(filter.test(null));
         assertSerializedEquals(filter);
@@ -96,8 +92,8 @@
      */
     @Test
     public void testNegate() {
-        final Literal<Feature,String>  literal = factory.literal("text");
-        final Filter<Feature>          operand = factory.isNull(literal);
+        final Expression<AbstractFeature,String> literal = factory.literal("text");
+        final Filter<AbstractFeature> operand = factory.isNull(literal);
         assertInstanceOf("Predicate.negate()", LogicalFilter.Not.class, operand.negate());
     }
 
@@ -108,12 +104,12 @@
      * @param  anyArity  the function creating a logical operator from an arbitrary number of operands.
      * @param  expected  expected evaluation result.
      */
-    private void create(final BiFunction<Filter<? super Feature>, Filter<? super Feature>, LogicalOperator<Feature>> binary,
-                        final Function<Collection<Filter<? super Feature>>, LogicalOperator<Feature>> anyArity,
+    private void create(final BiFunction<Filter<? super AbstractFeature>, Filter<? super AbstractFeature>, Filter<AbstractFeature>> binary,
+                        final Function<Collection<Filter<? super AbstractFeature>>, Filter<AbstractFeature>> anyArity,
                         final boolean expected)
     {
-        final Filter<Feature> f1 = factory.isNull(factory.literal("text"));
-        final Filter<Feature> f2 = factory.isNull(factory.literal(null));
+        final Filter<AbstractFeature> f1 = factory.isNull(factory.literal("text"));
+        final Filter<AbstractFeature> f2 = factory.isNull(factory.literal(null));
         try {
             binary.apply(null, null);
             fail("Creation with a null operand shall raise an exception.");
@@ -138,12 +134,12 @@
         /*
          * Test construction, evaluation and serialization.
          */
-        LogicalOperator<Feature> filter = binary.apply(f1, f2);
+        LogicalOperator<AbstractFeature> filter = (LogicalOperator<AbstractFeature>) binary.apply(f1, f2);
         assertArrayEquals(new Filter<?>[] {f1, f2}, filter.getOperands().toArray());
         assertEquals(expected, filter.test(null));
         assertSerializedEquals(filter);
 
-        filter = anyArity.apply(Arrays.asList(f1, f2, f1));
+        filter = (LogicalOperator<AbstractFeature>) anyArity.apply(Arrays.asList(f1, f2, f1));
         assertArrayEquals(new Filter<?>[] {f1, f2, f1}, filter.getOperands().toArray());
         assertEquals(expected, filter.test(null));
         assertSerializedEquals(filter);
@@ -157,11 +153,11 @@
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.addAttribute(String.class).setName("attNull");
         ftb.addAttribute(String.class).setName("attNotNull");
-        final Feature feature = ftb.setName("Test").build().newInstance();
+        final AbstractFeature feature = ftb.setName("Test").build().newInstance();
         feature.setPropertyValue("attNotNull", "a value");
 
-        final Filter<Feature> filterTrue  = factory.isNull(factory.property("attNull",    String.class));
-        final Filter<Feature> filterFalse = factory.isNull(factory.property("attNotNull", String.class));
+        final Filter<AbstractFeature> filterTrue  = factory.isNull(factory.property("attNull",    String.class));
+        final Filter<AbstractFeature> filterFalse = factory.isNull(factory.property("attNotNull", String.class));
 
         assertTrue (factory.and(filterTrue,  filterTrue ).test(feature));
         assertFalse(factory.and(filterFalse, filterTrue ).test(feature));
@@ -182,9 +178,9 @@
      */
     @Test
     public void testOptimization() {
-        final Filter<Feature> f1 = factory.isNull(factory.literal("text"));     // False
-        final Filter<Feature> f2 = factory.isNull(factory.literal(null));       // True
-        final Filter<Feature> f3 = factory.isNull(factory.property("*"));       // Indeterminate
+        final Filter<AbstractFeature> f1 = factory.isNull(factory.literal("text"));     // False
+        final Filter<AbstractFeature> f2 = factory.isNull(factory.literal(null));       // True
+        final Filter<AbstractFeature> f3 = factory.isNull(factory.property("*"));       // Indeterminate
         optimize(factory.and(f1, f2), Filter.exclude());
         optimize(factory.or (f1, f2), Filter.include());
         optimize(factory.and(f3, factory.not(f3)), Filter.exclude());
@@ -194,28 +190,28 @@
     /**
      * Verifies an optimization which is expected to evaluate immediately.
      */
-    private static void optimize(final Filter<Feature> original, final Filter<Feature> expected) {
-        final Filter<? super Feature> optimized = new Optimization().apply(original);
+    private static void optimize(final Filter<AbstractFeature> original, final Filter<AbstractFeature> expected) {
+        final Filter<? super AbstractFeature> optimized = new Optimization().apply(original);
         assertNotSame("Expected a new optimized filter.", original, optimized);
         assertSame("Second optimization should have no effect.", optimized, new Optimization().apply(optimized));
         assertSame("Expression should have been evaluated now.", expected, optimized);
     }
 
     /**
-     * Tests {@link Optimization} applied on logical filters when the {@link FeatureType} is known.
+     * Tests {@link Optimization} applied on logical filters when the {@link DefaultFeatureType} is known.
      */
     @Test
     public void testFeatureOptimization() {
         final String attribute = "population";
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.addAttribute(String.class).setName(attribute);
-        final FeatureType type = ftb.setName("Test").build();
-        final Feature instance = type.newInstance();
+        final DefaultFeatureType type = ftb.setName("Test").build();
+        final AbstractFeature instance = type.newInstance();
         instance.setPropertyValue("population", "1000");
         /*
          * Prepare an expression which divide the population value by 5.
          */
-        final Expression<Feature,Number> e = factory.divide(factory.property(attribute, Integer.class), factory.literal(5));
+        final Expression<AbstractFeature,Number> e = factory.divide(factory.property(attribute, Integer.class), factory.literal(5));
         final Optimization optimization = new Optimization();
         assertSame(e, optimization.apply(e));                       // No optimization.
         assertEquals(200, e.apply(instance).intValue());
@@ -224,7 +220,7 @@
          * The optimizer should compute an `ObjectConverter` in advance.
          */
         optimization.setFeatureType(type);
-        final Expression<? super Feature, ? extends Number> opt = optimization.apply(e);
+        final Expression<? super AbstractFeature, ? extends Number> opt = optimization.apply(e);
         assertEquals(200, e.apply(instance).intValue());
         assertNotSame(e, opt);
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/PeriodLiteral.java b/core/sis-feature/src/test/java/org/apache/sis/filter/PeriodLiteral.java
index 0a9dd86..d2213c1 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/PeriodLiteral.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/PeriodLiteral.java
@@ -21,16 +21,9 @@
 import org.apache.sis.test.TestUtilities;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.temporal.Period;
-import org.opengis.temporal.Duration;
-import org.opengis.temporal.RelativePosition;
-import org.opengis.temporal.TemporalPosition;
-import org.opengis.temporal.TemporalPrimitive;
-import org.opengis.temporal.TemporalGeometricPrimitive;
-import org.opengis.referencing.ReferenceIdentifier;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.internal.geoapi.filter.Literal;
+import org.apache.sis.internal.geoapi.temporal.Period;
 
 
 /**
@@ -43,7 +36,7 @@
  * @module
  */
 @SuppressWarnings("serial")
-final strictfp class PeriodLiteral implements Period, Literal<Feature,Period>, Serializable {
+final strictfp class PeriodLiteral implements Period, Literal<AbstractFeature,Period>, Serializable {
     /**
      * Period beginning and ending, in milliseconds since Java epoch.
      */
@@ -53,32 +46,22 @@
      * Returns the constant value held by this object.
      */
     @Override public Period getValue() {return this;}
+    @Override public Period apply(AbstractFeature input) {return this;}
 
     /** Returns a bound of this period. */
-    @Override public org.opengis.temporal.Instant getBeginning() {return instant(begin);}
-    @Override public org.opengis.temporal.Instant getEnding()    {return instant(end);}
+    @Override public org.apache.sis.internal.geoapi.temporal.Instant getBeginning() {return instant(begin);}
+    @Override public org.apache.sis.internal.geoapi.temporal.Instant getEnding()    {return instant(end);}
 
     /** Wraps the value that defines a period. */
-    private static org.opengis.temporal.Instant instant(final long t) {
-        return new org.opengis.temporal.Instant() {
+    private static org.apache.sis.internal.geoapi.temporal.Instant instant(final long t) {
+        return new org.apache.sis.internal.geoapi.temporal.Instant() {
             @Override public Date   getDate()  {return new Date(t);}
             @Override public String toString() {return "Instant[" + TestUtilities.format(getDate()) + '[';}
-
-            /** Not needed for the tests. */
-            @Override public ReferenceIdentifier getName()                           {throw new UnsupportedOperationException();}
-            @Override public TemporalPosition getTemporalPosition()                  {throw new UnsupportedOperationException();}
-            @Override public RelativePosition relativePosition(TemporalPrimitive o)  {throw new UnsupportedOperationException();}
-            @Override public Duration         distance(TemporalGeometricPrimitive o) {throw new UnsupportedOperationException();}
-            @Override public Duration         length()                               {throw new UnsupportedOperationException();}
         };
     }
 
     /** Not needed for the tests. */
-    @Override public ReferenceIdentifier getName()                           {throw new UnsupportedOperationException();}
-    @Override public RelativePosition relativePosition(TemporalPrimitive o)  {throw new UnsupportedOperationException();}
-    @Override public Duration         distance(TemporalGeometricPrimitive o) {throw new UnsupportedOperationException();}
-    @Override public Duration         length()                               {throw new UnsupportedOperationException();}
-    @Override public <N> Expression<Feature,N> toValueType(Class<N> target)  {throw new UnsupportedOperationException();}
+    @Override public <N> Expression<AbstractFeature,N> toValueType(Class<N> target) {throw new UnsupportedOperationException();}
 
     /**
      * Hash code value. Used by the tests for checking the results of deserialization.
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFilterTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFilterTest.java
index c601b20..b31335b 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFilterTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFilterTest.java
@@ -25,11 +25,8 @@
 import static org.apache.sis.internal.util.StandardDateFormat.MILLISECONDS_PER_DAY;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.TemporalOperator;
-import org.opengis.filter.TemporalOperatorName;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.internal.geoapi.filter.TemporalOperatorName;
 
 
 /**
@@ -45,13 +42,13 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,Object> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,Object> factory;
 
     /**
      * The filter to test. This field shall be assigned by each {@code testFoo()} method by invoking
      * a {@link #factory} method with {@link #expression1} and {@link #expression2} in arguments.
      */
-    private TemporalOperator<Feature> filter;
+    private Filter<AbstractFeature> filter;
 
     /**
      * The expression to test. They are the arguments to be given to {@link #factory} method.
@@ -79,7 +76,7 @@
     private void validate(final TemporalOperatorName name) {
         assertInstanceOf("Expected SIS implementation.", TemporalFilter.class, filter);
         assertEquals("name", name, filter.getOperatorType());
-        final List<Expression<? super Feature, ?>> operands = filter.getExpressions();
+        final List<Expression<? super AbstractFeature, ?>> operands = filter.getExpressions();
         assertEquals(2, operands.size());
         assertSame("expression1", expression1, operands.get(0));
         assertSame("expression2", expression2, operands.get(1));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/BandedIteratorTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/BandedIteratorTest.java
index 4d70f46..acede95 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/BandedIteratorTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/BandedIteratorTest.java
@@ -21,7 +21,6 @@
 import java.awt.image.DataBuffer;
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
-import org.opengis.coverage.grid.SequenceType;
 
 import static org.junit.Assert.*;
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java
index f7e0e92..ee74025 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java
@@ -19,7 +19,6 @@
 import java.awt.Dimension;
 import java.awt.Rectangle;
 import java.awt.image.DataBuffer;
-import org.opengis.coverage.grid.SequenceType;
 
 import static org.junit.Assert.*;
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/PixelIteratorTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/PixelIteratorTest.java
index 22f9749..9c037b7 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/PixelIteratorTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/PixelIteratorTest.java
@@ -31,7 +31,6 @@
 import java.awt.image.SampleModel;
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
-import org.opengis.coverage.grid.SequenceType;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.test.DependsOnMethod;
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java
index 22423e2..231bd46 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java
@@ -31,9 +31,7 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Property;
-import org.opengis.feature.Operation;
-import org.opengis.feature.IdentifiedType;
+import org.apache.sis.feature.AbstractOperation;
 
 
 /**
@@ -57,7 +55,7 @@
     }
 
     /**
-     * Tests {@link AttributeConvention#isGeometryAttribute(IdentifiedType)} method.
+     * Tests {@code AttributeConvention.isGeometryAttribute(IdentifiedType)} method.
      */
     @Test
     public void testIsGeometryAttribute() {
@@ -71,8 +69,8 @@
     }
 
     /**
-     * Tests {@link AttributeConvention#characterizedByCRS(IdentifiedType)} and
-     * {@link AttributeConvention#getCRSCharacteristic(Property)} methods.
+     * Tests {@code AttributeConvention.characterizedByCRS(IdentifiedType)} and
+     * {@code AttributeConvention.getCRSCharacteristic(Property)} methods.
      */
     @Test
     public void testGetCrsCharacteristic() {
@@ -95,15 +93,15 @@
         /*
          * Test again AttributeConvention.getCRSCharacteristic(…, PropertyType), but following link.
          */
-        final Operation link = FeatureOperations.link(Collections.singletonMap(DefaultAttributeType.NAME_KEY, "geom"), type);
+        final AbstractOperation link = FeatureOperations.link(Collections.singletonMap(DefaultAttributeType.NAME_KEY, "geom"), type);
         final DefaultFeatureType feat = new DefaultFeatureType(Collections.singletonMap(DefaultAttributeType.NAME_KEY, "feat"), false, null, type, link);
         assertEquals(HardCodedCRS.WGS84, AttributeConvention.getCRSCharacteristic(feat, link));
         assertNull(                      AttributeConvention.getCRSCharacteristic(null, link));
     }
 
     /**
-     * Tests {@link AttributeConvention#characterizedByMaximalLength(IdentifiedType)} and
-     * {@link AttributeConvention#getMaximalLengthCharacteristic(Property)} methods.
+     * Tests {@code AttributeConvention.characterizedByMaximalLength(IdentifiedType)} and
+     * {@code AttributeConvention.getMaximalLengthCharacteristic(Property)} methods.
      */
     @Test
     public void testGetMaximalLengthCharacteristic() {
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionNamesTest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionNamesTest.java
index ab56a4d..a53839c 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionNamesTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/FunctionNamesTest.java
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.internal.filter;
 
-import java.util.List;
-import java.util.Collections;
 import java.lang.reflect.Field;
 import org.apache.sis.internal.filter.sqlmm.SQLMM;
 import org.apache.sis.test.TestCase;
@@ -25,15 +23,6 @@
 
 import static org.junit.Assert.*;
 
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.ComparisonOperator;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.BetweenComparisonOperator;
-import org.opengis.filter.NullOperator;
-import org.opengis.filter.NilOperator;
-import org.opengis.filter.LikeOperator;
-import org.opengis.filter.Expression;
 
 
 /**
@@ -58,80 +47,6 @@
     }
 
     /**
-     * Base class for dummy implementation of filter.
-     */
-    private static abstract class FilterBase implements ComparisonOperator<Object> {
-        @Override public List<Expression<Object,?>> getExpressions() {return Collections.emptyList();}
-        @Override public boolean test(Object resource) {return false;}
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#PROPERTY_IS_NULL} name.
-     */
-    @Test
-    public void verifyPropertyIsNull() {
-        final class Instanciable extends FilterBase implements NullOperator<Object> {}
-        assertSame(new Instanciable().getOperatorType(), ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_NULL));
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#PROPERTY_IS_NIL} name.
-     */
-    @Test
-    public void verifyPropertyIsNil() {
-        final class Instanciable extends FilterBase implements NilOperator<Object> {}
-        assertSame(new Instanciable().getOperatorType(), ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_NIL));
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#PROPERTY_IS_LIKE} name.
-     */
-    @Test
-    public void verifyPropertyIsLike() {
-        final class Instanciable extends FilterBase implements LikeOperator<Object> {}
-        assertSame(new Instanciable().getOperatorType(), ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE));
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#PROPERTY_IS_BETWEEN} name.
-     */
-    @Test
-    public void verifyPropertyIsBetween() {
-        final class Instanciable extends FilterBase implements BetweenComparisonOperator<Object> {}
-        assertSame(new Instanciable().getOperatorType(), ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN));
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#Literal} name.
-     */
-    @Test
-    public void verifyLiteral() {
-        final Literal<Object,Object> expression = new Literal<Object,Object>() {
-            @Override public Object getValue() {return null;}
-            @Override public <N> Expression<Object, N> toValueType(Class<N> target) {
-                throw new UnsupportedOperationException();
-            }
-        };
-        assertEquals(expression.getFunctionName().tip().toString(), FunctionNames.Literal);
-    }
-
-    /**
-     * Verifies the {@value FunctionNames#ValueReference} name.
-     */
-    @Test
-    public void verifyValueReference() {
-        // TODO: use diamond operator with JDK9.
-        final ValueReference<Object,Object> expression = new ValueReference<Object,Object>() {
-            @Override public String getXPath()      {return null;}
-            @Override public Object apply(Object o) {return null;}
-            @Override public <N> Expression<Object,N> toValueType(Class<N> target) {
-                throw new UnsupportedOperationException();
-            }
-        };
-        assertEquals(expression.getFunctionName().tip().toString(), FunctionNames.ValueReference);
-    }
-
-    /**
      * Verifies SQLMM names.
      */
     @Test
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java
index a228df9..2291bea 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/RegistryTestCase.java
@@ -23,7 +23,6 @@
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.filter.DefaultFilterFactory;
 import org.apache.sis.filter.Optimization;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.WraparoundMethod;
@@ -45,12 +44,11 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.ValueReference;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.filter.DefaultFilterFactory;
+import org.apache.sis.internal.geoapi.filter.Literal;
 
 
 /**
@@ -75,7 +73,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature, G, Object> factory;
+    private final DefaultFilterFactory<AbstractFeature, G, Object> factory;
 
     /**
      * The geometry library used by this factory.
@@ -100,7 +98,7 @@
      *
      * @see #evaluate(String, Object...)
      */
-    private Expression<Feature,?> function;
+    private Expression<AbstractFeature,?> function;
 
     /**
      * Tolerance threshold for assertions. Default value is 0.
@@ -167,11 +165,11 @@
      * @param  type  the type of value in the property.
      * @return a feature with a property of the given type.
      */
-    private Feature createFeatureWithGeometry(final Class<?> type) {
+    private AbstractFeature createFeatureWithGeometry(final Class<?> type) {
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.addAttribute(type).setName(P_NAME);
-        final FeatureType mockType = ftb.setName("Test").build();
-        final Feature feature = mockType.newInstance();
+        final DefaultFeatureType mockType = ftb.setName("Test").build();
+        final AbstractFeature feature = mockType.newInstance();
         feature.setPropertyValue(P_NAME, geometry);
         return feature;
     }
@@ -186,7 +184,7 @@
      */
     private Object evaluate(final String name, final Object... values) {
         @SuppressWarnings({"unchecked","rawtypes"})
-        final Literal<Feature,?>[] parameters = new Literal[values.length];
+        final Expression<AbstractFeature,?>[] parameters = new Literal[values.length];
         for (int i=0; i<values.length; i++) {
             parameters[i] = factory.literal(values[i]);
         }
@@ -260,7 +258,7 @@
 
     /**
      * Tests SQL/MM {@link ST_Transform} function.
-     * The geometry to transform is obtained from a {@link Feature}.
+     * The geometry to transform is obtained from a {@code Feature}.
      */
     @Test
     public void testTransform() {
@@ -274,11 +272,11 @@
          */
         geometry = library.createPoint(10, 30);
         setGeometryCRS(HardCodedCRS.WGS84);
-        Feature feature = createFeatureWithGeometry(library.pointClass);
+        AbstractFeature feature = createFeatureWithGeometry(library.pointClass);
         /*
          * Test transform function using the full CRS object, then using only EPSG code.
          */
-        final ValueReference<Feature,?> ref = factory.property(P_NAME, library.pointClass);
+        final Expression<AbstractFeature,?> ref = factory.property(P_NAME, library.pointClass);
         function = factory.function("ST_Transform", ref, factory.literal(HardCodedCRS.WGS84_LATITUDE_FIRST));
         assertPointEquals(function.apply(feature), HardCodedCRS.WGS84_LATITUDE_FIRST, 30, 10);
 
@@ -288,7 +286,7 @@
 
     /**
      * Tests SQL/MM {@code ST_Buffer} function.
-     * The geometry is specified as a literal, so there is no need to build {@link Feature} instances.
+     * The geometry is specified as a literal, so there is no need to build {@code Feature} instances.
      */
     @Test
     public void testBuffer() {
@@ -337,7 +335,7 @@
         assertEnvelopeEquals(result, false, null, 12, 3.3, 13.1, 5.7);
 
         // After testing literal data, try to extract data from a feature.
-        Feature feature = createFeatureWithGeometry(library.polylineClass);
+        AbstractFeature feature = createFeatureWithGeometry(library.polylineClass);
         function = factory.function("ST_Envelope", factory.property(P_NAME, library.polylineClass));
         result = function.apply(feature);
         assertEnvelopeEquals(result, false, null, 12, 3.3, 13.1, 5.7);
@@ -355,8 +353,8 @@
         assertEquals(Boolean.FALSE, evaluate("ST_Intersects", point, geometry));
 
         // Border should intersect. Also use Feature instead of Literal as a source.
-        final Feature feature = createFeatureWithGeometry(library.polygonClass);
-        final ValueReference<Feature,?> ref = factory.property(P_NAME, library.polygonClass);
+        final AbstractFeature feature = createFeatureWithGeometry(library.polygonClass);
+        final Expression<AbstractFeature,?> ref = factory.property(P_NAME, library.polygonClass);
         point = library.createPoint(0.2, 0.3);
         function = factory.function("ST_Intersects", ref, factory.literal(point));
         assertEquals(Boolean.TRUE, function.apply(feature));
@@ -473,7 +471,7 @@
         /*
          * Optimization should evaluate the point immediately.
          */
-        final Expression<? super Feature, ?> optimized = new Optimization().apply(function);
+        final Expression<? super AbstractFeature, ?> optimized = new Optimization().apply(function);
         assertNotSame("Optimization should produce a new expression.", function, optimized);
         assertInstanceOf("Expected immediate expression evaluation.", Literal.class, optimized);
         assertPointEquals(((Literal) optimized).getValue(), HardCodedCRS.WGS84_LATITUDE_FIRST, 30, 10);
@@ -492,7 +490,7 @@
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.addAttribute(library.pointClass).setName(P_NAME).setCRS(HardCodedCRS.WGS84);
         optimization.setFeatureType(ftb.setName("Test").build());
-        final Expression<? super Feature, ?> optimized = optimization.apply(function);
+        final Expression<? super AbstractFeature, ?> optimized = optimization.apply(function);
         assertNotSame("Optimization should produce a new expression.", function, optimized);
         /*
          * Get the second parameter, which should be a literal, and get the point coordinates.
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/SQLMMTest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/SQLMMTest.java
index 9d88970..76ebfea 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/SQLMMTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/filter/sqlmm/SQLMMTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.filter.sqlmm;
 
+import java.util.function.Function;
 import java.util.function.BiFunction;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -31,10 +32,8 @@
 import static org.opengis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.filter.Literal;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.filter.FilterFactory;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.filter.Expression;
 
 
 /**
@@ -51,7 +50,7 @@
     /**
      * The factory to use for creating the objects to test.
      */
-    private final FilterFactory<Feature,Object,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,Object,?> factory;
 
     /**
      * Creates a new test case.
@@ -80,7 +79,7 @@
     @Test
     public void testST_GeomFromText() {
         final String wkt = "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))";
-        final Expression<Feature,?> exp = factory.function("ST_GeomFromText", factory.literal(wkt), factory.literal(4326));
+        final Function<AbstractFeature,?> exp = factory.function("ST_GeomFromText", factory.literal(wkt), factory.literal(4326));
         final Object value = exp.apply(null);
         assertInstanceOf("Expected JTS implementation.", Polygon.class, value);
         final Polygon polygon = (Polygon) value;
@@ -122,20 +121,20 @@
      * @throws FactoryException if an error occurred while fetching the CRS from a JTS geometry.
      */
     private void verifyPoint(final CoordinateReferenceSystem expectedCRS,
-                             final BiFunction<Expression<Feature, Double>,
-                                              Expression<Feature, Double>,
-                                              Expression<Feature, ?>[]> argumentBundler)
+                             final BiFunction<Expression<AbstractFeature, Double>,
+                                              Expression<AbstractFeature, Double>,
+                                              Expression<AbstractFeature, ?>[]> argumentBundler)
             throws FactoryException
     {
-        final Literal<Feature, Double> x = factory.literal(1.0);
-        final Literal<Feature, Double> y = factory.literal(2.0);
-        Expression<Feature, ?> fn = factory.function("ST_Point", argumentBundler.apply(x, y));
+        final Expression<AbstractFeature, Double> x = factory.literal(1.0);
+        final Expression<AbstractFeature, Double> y = factory.literal(2.0);
+        Expression<AbstractFeature, ?> fn = factory.function("ST_Point", argumentBundler.apply(x, y));
         Object rawPoint = fn.apply(null);
         assertInstanceOf("ST_Point should create a Point geometry", Point.class, rawPoint);
         Point point = (Point) rawPoint;
         CoordinateReferenceSystem pointCRS = JTS.getCoordinateReferenceSystem(point);
         assertEquals("Point CRS", expectedCRS, pointCRS);
-        assertEquals(point.getX(), x.getValue(), STRICT);
-        assertEquals(point.getY(), y.getValue(), STRICT);
+        assertEquals(point.getX(), x.apply(null), STRICT);
+        assertEquals(point.getY(), y.apply(null), STRICT);
     }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java b/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java
index 0444535..349128b 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java
@@ -21,7 +21,7 @@
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
 import java.awt.geom.PathIterator;
-import org.opengis.coverage.grid.SequenceType;
+import org.apache.sis.image.SequenceType;
 import org.apache.sis.image.PixelIterator;
 
 import static org.junit.Assert.*;
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/feature/FeatureComparator.java b/core/sis-feature/src/test/java/org/apache/sis/test/feature/FeatureComparator.java
index 316f6ad..d01ce51 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/feature/FeatureComparator.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/feature/FeatureComparator.java
@@ -34,13 +34,12 @@
 import static org.opengis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAssociationRole;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.AbstractOperation;
 
 
 /**
@@ -56,22 +55,22 @@
     /**
      * The expected feature, or {@code null} if comparing only feature type.
      */
-    private final Feature expectedInstance;
+    private final AbstractFeature expectedInstance;
 
     /**
      * The expected feature type.
      */
-    private final FeatureType expectedType;
+    private final DefaultFeatureType expectedType;
 
     /**
      * The actual feature, or {@code null} if comparing only feature type.
      */
-    private final Feature actualInstance;
+    private final AbstractFeature actualInstance;
 
     /**
      * The actual feature type.
      */
-    private final FeatureType actualType;
+    private final DefaultFeatureType actualType;
 
     /**
      * The fully-qualified name of properties to ignore in comparisons.
@@ -102,7 +101,7 @@
      * to feature types and property types. The default value is {@code false}.
      *
      * @see #compare()
-     * @see IdentifiedType#getDefinition()
+     * @see AbstractIdentifiedType#getDefinition()
      */
     public boolean ignoreDefinition;
 
@@ -111,7 +110,7 @@
      * to feature types and property types. The default value is {@code false}.
      *
      * @see #compare()
-     * @see IdentifiedType#getDesignation()
+     * @see AbstractIdentifiedType#getDesignation()
      */
     public boolean ignoreDesignation;
 
@@ -120,7 +119,7 @@
      * to feature types and property types. The default value is {@code false}.
      *
      * @see #compare()
-     * @see IdentifiedType#getDescription()
+     * @see AbstractIdentifiedType#getDescription()
      */
     public boolean ignoreDescription;
 
@@ -135,7 +134,7 @@
      * @param  expected  the expected feature instance.
      * @param  actual    the actual feature instance.
      */
-    public FeatureComparator(final Feature expected, final Feature actual) {
+    public FeatureComparator(final AbstractFeature expected, final AbstractFeature actual) {
         ArgumentChecks.ensureNonNull("expected", expected);
         ArgumentChecks.ensureNonNull("actual", actual);
         expectedInstance = expected;
@@ -150,7 +149,7 @@
      * @param  expected  the expected feature type.
      * @param  actual    the actual feature type.
      */
-    public FeatureComparator(final FeatureType expected, final FeatureType actual) {
+    public FeatureComparator(final DefaultFeatureType expected, final DefaultFeatureType actual) {
         ArgumentChecks.ensureNonNull("expected", expected);
         ArgumentChecks.ensureNonNull("actual",   actual);
         expectedInstance = null;
@@ -182,16 +181,16 @@
      * @param  actual    the actual type.
      * @throws AssertionError if the actual type is not equal to the expected type.
      */
-    private void compareType(final IdentifiedType expected, final IdentifiedType actual) {
+    private void compareType(final AbstractIdentifiedType expected, final AbstractIdentifiedType actual) {
         boolean recognized = false;
-        if (expected instanceof FeatureType) {
-            assertInstanceOf(path(), FeatureType.class, actual);
-            compareFeatureType((FeatureType) expected, (FeatureType) actual);
+        if (expected instanceof DefaultFeatureType) {
+            assertInstanceOf(path(), DefaultFeatureType.class, actual);
+            compareFeatureType((DefaultFeatureType) expected, (DefaultFeatureType) actual);
             recognized = true;
         }
-        if (expected instanceof PropertyType) {
-            assertInstanceOf(path(), PropertyType.class, actual);
-            comparePropertyType((PropertyType) expected, (PropertyType) actual);
+        if (expected instanceof AbstractIdentifiedType) {
+            assertInstanceOf(path(), AbstractIdentifiedType.class, actual);
+            comparePropertyType((AbstractIdentifiedType) expected, (AbstractIdentifiedType) actual);
             recognized = true;
         }
         if (!recognized) {
@@ -206,7 +205,7 @@
      * @param  actual    the actual type.
      * @throws AssertionError if the actual type is not equal to the expected type.
      */
-    private void compareFeatureType(final FeatureType expected, final FeatureType actual) {
+    private void compareFeatureType(final DefaultFeatureType expected, final DefaultFeatureType actual) {
         compareIdentifiedType(expected, actual);
 
         // TODO: put messages in lambda functionw with JUnit 5.
@@ -216,12 +215,12 @@
          * Compare all properties that are not ignored.
          * Properties are removed from the `actualProperties` list as we found them.
          */
-        final List<PropertyType> actualProperties = new ArrayList<>(actual.getProperties(false));
+        final List<AbstractIdentifiedType> actualProperties = new ArrayList<>(actual.getProperties(false));
         actualProperties.removeIf(this::isIgnored);
-        for (final PropertyType pte : expected.getProperties(false)) {
+        for (final AbstractIdentifiedType pte : expected.getProperties(false)) {
             if (!isIgnored(pte)) {
                 final String tip = push(pte.getName().toString());
-                PropertyType pta = findAndRemove(actualProperties, pte.getName());
+                AbstractIdentifiedType pta = findAndRemove(actualProperties, pte.getName());
                 comparePropertyType(pte, pta);
                 pull(tip);
             }
@@ -233,7 +232,7 @@
             final StringBuilder b = new StringBuilder(path())
                     .append("Actual type contains a property not declared in expected type:")
                     .append(System.lineSeparator());
-            for (final PropertyType pta : actualProperties) {
+            for (final AbstractIdentifiedType pta : actualProperties) {
                 b.append("  ").append(pta.getName()).append(System.lineSeparator());
             }
             fail(b.toString());
@@ -248,9 +247,9 @@
      * @param  actual    the actual instance.
      * @throws AssertionError if the actual instance is not equal to the expected instance.
      */
-    private void compareFeature(final Feature expected, final Feature actual) {
+    private void compareFeature(final AbstractFeature expected, final AbstractFeature actual) {
         compareFeatureType(expected.getType(), actual.getType());
-        for (final PropertyType p : expected.getType().getProperties(true)) {
+        for (final AbstractIdentifiedType p : expected.getType().getProperties(true)) {
             if (isIgnored(p)) {
                 continue;
             }
@@ -263,8 +262,8 @@
             while (expectedIter.hasNext()) {
                 final Object expectedElement = expectedIter.next();
                 final Object actualElement = actualIter.next();
-                if (expectedElement instanceof Feature) {
-                    compareFeature((Feature) expectedElement, (Feature) actualElement);
+                if (expectedElement instanceof AbstractFeature) {
+                    compareFeature((AbstractFeature) expectedElement, (AbstractFeature) actualElement);
                 } else {
                     assertEquals(expectedElement, actualElement);
                 }
@@ -280,18 +279,18 @@
      * @param  actual    the actual property.
      * @throws AssertionError if the actual property is not equal to the expected property.
      */
-    private void comparePropertyType(final PropertyType expected, final PropertyType actual) {
-        if (expected instanceof AttributeType) {
-            assertInstanceOf(path(), AttributeType.class, actual);
-            compareAttribute((AttributeType) expected, (AttributeType) actual);
+    private void comparePropertyType(final AbstractIdentifiedType expected, final AbstractIdentifiedType actual) {
+        if (expected instanceof DefaultAttributeType) {
+            assertInstanceOf(path(), DefaultAttributeType.class, actual);
+            compareAttribute((DefaultAttributeType) expected, (DefaultAttributeType) actual);
         }
-        if (expected instanceof FeatureAssociationRole) {
-            assertInstanceOf(path(), FeatureAssociationRole.class, actual);
-            compareFeatureAssociationRole((FeatureAssociationRole) expected, (FeatureAssociationRole) actual);
+        if (expected instanceof DefaultAssociationRole) {
+            assertInstanceOf(path(), DefaultAssociationRole.class, actual);
+            compareFeatureAssociationRole((DefaultAssociationRole) expected, (DefaultAssociationRole) actual);
         }
-        if (expected instanceof Operation) {
-            assertInstanceOf(path(), Operation.class, actual);
-            compareOperation((Operation) expected, (Operation) actual);
+        if (expected instanceof AbstractOperation) {
+            assertInstanceOf(path(), AbstractOperation.class, actual);
+            compareOperation((AbstractOperation) expected, (AbstractOperation) actual);
         }
     }
 
@@ -302,21 +301,21 @@
      * @param  actual    the actual property.
      * @throws AssertionError if the actual property is not equal to the expected property.
      */
-    private void compareAttribute(final AttributeType<?> expected, final AttributeType<?> actual) {
+    private void compareAttribute(final DefaultAttributeType<?> expected, final DefaultAttributeType<?> actual) {
         compareIdentifiedType(expected, actual);
         assertEquals(path() + "Value classe differ",  expected.getValueClass(),   expected.getValueClass());
         assertEquals(path() + "Default value differ", expected.getDefaultValue(), expected.getDefaultValue());
 
-        final Map<String, AttributeType<?>> expectedChrs = expected.characteristics();
-        final Map<String, AttributeType<?>> actualChrs = actual.characteristics();
+        final Map<String, DefaultAttributeType<?>> expectedChrs = expected.characteristics();
+        final Map<String, DefaultAttributeType<?>> actualChrs = actual.characteristics();
         final List<String> actualChrNames = new ArrayList<>(actualChrs.keySet());
         actualChrNames.removeIf((p) -> ignoredCharacteristics.contains(p));
 
-        for (final Map.Entry<String, AttributeType<?>> entry : expectedChrs.entrySet()) {
+        for (final Map.Entry<String, DefaultAttributeType<?>> entry : expectedChrs.entrySet()) {
             final String p = entry.getKey();
             if (!ignoredCharacteristics.contains(p)) {
-                final AttributeType<?> expectedChr = entry.getValue();
-                final AttributeType<?> actualChr = actualChrs.get(p);
+                final DefaultAttributeType<?> expectedChr = entry.getValue();
+                final DefaultAttributeType<?> actualChr = actualChrs.get(p);
                 final String tip = push("characteristic(" + p + ')');
                 assertNotNull(path(), actualChr);
                 assertTrue(actualChrNames.remove(p));
@@ -345,7 +344,7 @@
      * @param  actual    the actual property.
      * @throws AssertionError if the actual property is not equal to the expected property.
      */
-    private void compareFeatureAssociationRole(final FeatureAssociationRole expected, final FeatureAssociationRole actual) {
+    private void compareFeatureAssociationRole(final DefaultAssociationRole expected, final DefaultAssociationRole actual) {
         compareIdentifiedType(expected, actual);
         assertEquals(path() + "Minimum occurences differ", expected.getMinimumOccurs(), actual.getMinimumOccurs());
         assertEquals(path() + "Maximum occurences differ", expected.getMaximumOccurs(), actual.getMaximumOccurs());
@@ -361,7 +360,7 @@
      * @param  actual    the actual property.
      * @throws AssertionError if the actual property is not equal to the expected property.
      */
-    private void compareOperation(final Operation expected, final Operation actual) {
+    private void compareOperation(final AbstractOperation expected, final AbstractOperation actual) {
         compareIdentifiedType(expected, actual);
         assertEquals(expected.getParameters(), actual.getParameters());
         final String tip = push("operation-actual(" + expected.getResult().getName() + ')');
@@ -376,7 +375,7 @@
      * @param  actual    the actual type.
      * @throws AssertionError if the actual type is not equal to the expected type.
      */
-    private void compareIdentifiedType(final IdentifiedType expected, final IdentifiedType actual) {
+    private void compareIdentifiedType(final AbstractIdentifiedType expected, final AbstractIdentifiedType actual) {
         assertEquals(path() + "Name differ", expected.getName(), actual.getName());
         if (!ignoreDefinition) {
             assertEquals(path() + "Definition differ", expected.getDefinition(), actual.getDefinition());
@@ -422,7 +421,7 @@
     /**
      * Returns {@code true} if the given property should be ignored in feature comparisons.
      */
-    private boolean isIgnored(final PropertyType property) {
+    private boolean isIgnored(final AbstractIdentifiedType property) {
         return ignoredProperties.contains(property.getName().toString());
     }
 
@@ -430,10 +429,10 @@
      * Searches for a property of the given name in the given list and remove the property from that list.
      * If the property is not found, then this method fails.
      */
-    private PropertyType findAndRemove(final Collection<PropertyType> properties, final GenericName name) {
-        final Iterator<PropertyType> it = properties.iterator();
+    private AbstractIdentifiedType findAndRemove(final Collection<AbstractIdentifiedType> properties, final GenericName name) {
+        final Iterator<AbstractIdentifiedType> it = properties.iterator();
         while (it.hasNext()) {
-            final PropertyType pt = it.next();
+            final AbstractIdentifiedType pt = it.next();
             if (pt.getName().equals(name)) {
                 it.remove();
                 return pt;
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index db88eca..e22cba9 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -51,7 +51,6 @@
     org.apache.sis.feature.FeatureFormatTest.class,
     org.apache.sis.feature.FeaturesTest.class,
     org.apache.sis.filter.XPathTest.class,
-    org.apache.sis.filter.CapabilitiesTest.class,
     org.apache.sis.filter.LeafExpressionTest.class,
     org.apache.sis.filter.LogicalFilterTest.class,
     org.apache.sis.filter.IdentifierFilterTest.class,
diff --git a/core/sis-metadata/pom.xml b/core/sis-metadata/pom.xml
index e37fcbf..95631f3 100644
--- a/core/sis-metadata/pom.xml
+++ b/core/sis-metadata/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/Interim.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/Interim.java
new file mode 100644
index 0000000..6db3510
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/Interim.java
@@ -0,0 +1,70 @@
+/*
+ * 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.geoapi.evolution;
+
+import java.lang.reflect.Method;
+import org.apache.sis.util.Static;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.internal.system.Modules;
+import org.opengis.geometry.Envelope;
+import org.opengis.geometry.Geometry;
+
+import static java.util.logging.Logger.getLogger;
+
+
+/**
+ * Temporary methods used until a new major GeoAPI release provides the missing functionalities.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   0.8
+ * @module
+ */
+public final class Interim extends Static {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private Interim() {
+    }
+
+    /**
+     * Returns the return type of the given method, or the interim type if the method is annotated
+     * with {@link InterimType}.
+     *
+     * @param  method  the method from which to get the return type.
+     * @return the return type or the interim type.
+     */
+    public static Class<?> getReturnType(final Method method) {
+        final InterimType an = method.getAnnotation(InterimType.class);
+        return (an != null) ? an.value() : method.getReturnType();
+    }
+
+    /**
+     * Invokes {@code Geometry.getEnvelope()} if that method exists.
+     *
+     * @param  geometry  the geometry from which to get the envelope.
+     * @return the geometry envelope, or {@code null} if none.
+     */
+    public static Envelope getEnvelope(final Geometry geometry) {
+        try {
+            return (Envelope) geometry.getClass().getMethod("getEnvelope").invoke(geometry);
+        } catch (ReflectiveOperationException | ClassCastException e) {
+            Logging.recoverableException(getLogger(Modules.METADATA), Interim.class, "getEnvelope", e);
+            return null;
+        }
+    }
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/InterimType.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/InterimType.java
new file mode 100644
index 0000000..ace7308
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/InterimType.java
@@ -0,0 +1,45 @@
+/*
+ * 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.geoapi.evolution;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+
+
+/**
+ * Identifies an interim class to use until an official GeoAPI class or interface is released.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.8
+ * @since   0.8
+ * @module
+ *
+ * @see Interim#getReturnType(Method)
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface InterimType {
+    /**
+     * The interim Apache SIS class to use until the GeoAPI class or interface is released.
+     *
+     * @return Apache SIS class to use in the interim.
+     */
+    Class<?> value();
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeList.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeList.java
new file mode 100644
index 0000000..09df869
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeList.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014 desruisseaux.
+ *
+ * Licensed 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.geoapi.evolution;
+
+import java.util.List;
+import java.util.ArrayList;
+import org.opengis.annotation.UML;
+import org.opengis.util.CodeList;
+
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
+
+/**
+ * Placeholder for code list not yet available in GeoAPI.
+ * Currently defines constants mostly for {@code org.opengis.metadata.citation.TelephoneType},
+ * but constants for other code list can be constructed like below:
+ *
+ * {@preformat java
+ *   operation.getDistributedComputingPlatforms().add(UnsupportedCodeList.valueOf("SOAP"));
+ * }
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   0.5
+ * @module
+ */
+public final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 7205015191869240829L;
+
+    /**
+     * The list of constants defined in this code list.
+     */
+    private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>(3);
+
+    /**
+     * A frequently used code list element.
+     */
+    @UML(identifier="voice", obligation=CONDITIONAL, specification=ISO_19115)
+    public static final CodeList<?> VOICE = new UnsupportedCodeList("VOICE");
+
+    /**
+     * A frequently used code list element.
+     */
+    @UML(identifier="facsimile", obligation=CONDITIONAL, specification=ISO_19115)
+    public static final CodeList<?> FACSIMILE = new UnsupportedCodeList("FACSIMILE");
+
+    /**
+     * A frequently used code list element.
+     */
+    @UML(identifier="WebServices", obligation=CONDITIONAL, specification=ISO_19115)
+    public static final CodeList<?> WEB_SERVICES = new UnsupportedCodeList("WEB_SERVICES");
+
+    /**
+     * Constructor for new code list element.
+     *
+     * @param name The code list name.
+     */
+    private UnsupportedCodeList(String name) {
+        super(name, VALUES);
+    }
+
+    /**
+     * Returns the list of codes of the same kind than this code list element.
+     *
+     * @return All code values for this code list.
+     */
+    @Override
+    public UnsupportedCodeList[] family() {
+        synchronized (VALUES) {
+            return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+        }
+    }
+
+    /**
+     * Returns the telephone type that matches the given string, or returns a new one if none match it.
+     *
+     * @param code The name of the code to fetch or to create.
+     * @return A code matching the given name.
+     */
+    public static UnsupportedCodeList valueOf(String code) {
+        return valueOf(UnsupportedCodeList.class, code);
+    }
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeListAdapter.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeListAdapter.java
new file mode 100644
index 0000000..459adf2
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeListAdapter.java
@@ -0,0 +1,165 @@
+/*
+ * 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.geoapi.evolution;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import org.opengis.util.CodeList;
+import org.apache.sis.util.iso.Types;
+import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.internal.jaxb.cat.CodeListUID;
+
+
+/**
+ * An adapter for {@link UnsupportedCodeList}, in order to implement the ISO 19115-3 standard.
+ * See {@link org.apache.sis.internal.jaxb.cat.CodeListAdapter} for more information.
+ *
+ * @param <ValueType> The subclass implementing this adapter.
+ *
+ * @author  Cédric Briançon (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.5
+ * @since   0.5
+ * @module
+ */
+public abstract class UnsupportedCodeListAdapter<ValueType extends UnsupportedCodeListAdapter<ValueType>>
+        extends XmlAdapter<ValueType,CodeList<?>>
+{
+    /**
+     * The value of the {@link CodeList}.
+     */
+    protected CodeListUID identifier;
+
+    /**
+     * Empty constructor for subclasses only.
+     */
+    protected UnsupportedCodeListAdapter() {
+    }
+
+    /**
+     * Creates a wrapper for a {@link CodeList}, in order to handle the format specified in ISO-19139.
+     *
+     * @param  value  the value of the {@link CodeList} to be marshalled.
+     */
+    protected UnsupportedCodeListAdapter(final CodeListUID value) {
+        identifier = value;
+    }
+
+    /**
+     * Wraps the code into an adapter.
+     * Most implementations will be like below:
+     *
+     * {@preformat java
+     *     return new ValueType(value);
+     * }
+     *
+     * @param  value  the value of {@link CodeList} to be marshalled.
+     * @return the wrapper for the code list value.
+     */
+    protected abstract ValueType wrap(CodeListUID value);
+
+    /**
+     * Returns the name of the code list class.
+     *
+     * @return the code list class name.
+     */
+    protected abstract String getCodeListName();
+
+    /**
+     * Substitutes the adapter value read from an XML stream by the object which will
+     * contains the value. JAXB calls automatically this method at unmarshalling time.
+     *
+     * @param  adapter  the adapter for this metadata value.
+     * @return a code list which represents the metadata value.
+     */
+    @Override
+    public final CodeList<?> unmarshal(final ValueType adapter) {
+        if (adapter == null) {
+            return null;
+        }
+        return Types.forCodeName(UnsupportedCodeList.class, adapter.identifier.toString(), true);
+    }
+
+    /**
+     * Substitutes the code list by the adapter to be marshalled into an XML file or stream.
+     * JAXB calls automatically this method at marshalling time.
+     *
+     * @param  value  the code list value.
+     * @return the adapter for the given code list.
+     */
+    @Override
+    public final ValueType marshal(final CodeList<?> value) {
+        if (value == null) {
+            return null;
+        }
+        final String name = value.name();
+        final int length = name.length();
+        final StringBuilder buffer = new StringBuilder(length);
+        final String codeListValue = toIdentifier(name, buffer, false);
+        buffer.setLength(0);
+        return wrap(new CodeListUID(Context.current(), getCodeListName(), codeListValue,
+                null, toIdentifier(name, buffer, true)));
+    }
+
+    /**
+     * Converts the given Java constant name to something hopefully close to the UML identifier,
+     * or close to the textual value to put in the XML. This method convert the Java constant name
+     * to camel case if {@code isValue} is {@code true}, or to lower cases with word separated by
+     * spaces if {@code isValue} is {@code true}.
+     *
+     * @param  name    The Java constant name (e.g. {@code WEB_SERVICES}).
+     * @param  buffer  An initially empty buffer to use for creating the identifier.
+     * @param  isValue {@code false} for the {@code codeListValue} attribute, or {@code true} for the XML value.
+     * @return The identifier (e.g. {@code "webServices"} or {@code "Web services"}).
+     */
+    protected String toIdentifier(final String name, final StringBuilder buffer, final boolean isValue) {
+        final int length = name.length();
+        boolean toUpper = isValue;
+        for (int i=0; i<length;) {
+            int c = name.codePointAt(i);
+            i += Character.charCount(c);
+            if (c == '_') {
+                if (isValue) {
+                    c = ' ';
+                } else {
+                    toUpper = true;
+                    continue;
+                }
+            }
+            if (toUpper) {
+                c = Character.toUpperCase(c);
+                toUpper = false;
+            } else {
+                c = Character.toLowerCase(c);
+            }
+            buffer.appendCodePoint(c);
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Invoked by JAXB on marshalling. Subclasses must override this
+     * method with the appropriate {@code @XmlElement} annotation.
+     *
+     * @return The {@code CodeList} value to be marshalled.
+     */
+    public abstract CodeListUID getElement();
+
+    /*
+     * We do not define setter method (even abstract) since it seems to confuse JAXB.
+     * It is subclasses responsibility to define the setter method.
+     */
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/package-info.java
new file mode 100644
index 0000000..c1f54c4
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/package-info.java
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides a transition path for new GeoAPI elements not yet published in a formal release.
+ * We try to avoid putting a copy of those new elements in Apache SIS, since it would break
+ * compatibility when they would be removed in favor of GeoAPI elements. The approach taken
+ * is rather to use in the new API the most immediate parent available in a GeoAPI release.
+ * For example for new code list classes, this is {@code CodeList<?>}. The Javadoc for such
+ * API shall contain a warning. See {@code warning-templates.txt} for some proposals.
+ *
+ * <p><STRONG>Do not use!</STRONG></p>
+ *
+ * This package is for internal use by SIS only. Classes in this package
+ * may change in incompatible ways in any future version without notice.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   0.5
+ * @module
+ */
+package org.apache.sis.internal.geoapi.evolution;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/warning-templates.txt b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/warning-templates.txt
new file mode 100644
index 0000000..b81aad9
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/warning-templates.txt
@@ -0,0 +1,33 @@
+Suggestions for the Javadoc of methods having a CodeList<?> return type
+(replace "NewCodeList" and "3.1" by appropriate values):
+
+     *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The argument type will be changed to the {@code NewCodeList} code list when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@preformat java
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     * }
+     * </div>
+     *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java
index b33c925..9b1c94b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java
@@ -21,6 +21,9 @@
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.metadata.iso.citation.Citations;
 
+// Branch-dependent imports
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * An entry in {@link org.apache.sis.xml.IdentifierMap}. This class implements both the {@link AbstractMap.Entry}
@@ -32,7 +35,7 @@
  * @since   0.3
  * @module
  */
-final class IdentifierMapEntry extends AbstractMap.SimpleEntry<Citation,String> implements Identifier {
+final class IdentifierMapEntry extends AbstractMap.SimpleEntry<Citation,String> implements ReferenceIdentifier {
     /**
      * For cross-version compatibility.
      */
@@ -74,15 +77,28 @@
     }
 
     /**
+     * Returns {@code null} since this class does not hold version information.
+     *
+     * @return {@code null}.
+     *
+     * @since 0.5
+     */
+    @Override
+    public String getVersion() {
+        return null;
+    }
+
+    /**
      * Same than the above, but as an immutable entry. We use this implementation when the
      * entry has been created on-the-fly at iteration time rather than being stored in the
      * identifier collection.
      */
-    static final class Immutable extends AbstractMap.SimpleImmutableEntry<Citation,String> implements Identifier {
+    static final class Immutable extends AbstractMap.SimpleImmutableEntry<Citation,String> implements ReferenceIdentifier {
         private static final long serialVersionUID = -6857931598565368465L;
         Immutable(Citation authority, String code) {super(authority, code);}
         @Override public Citation            getAuthority()   {return getKey();}
         @Override public String              getCode()        {return getValue();}
         @Override public String              getCodeSpace()   {return Citations.toCodeSpace(getAuthority());}
+        @Override public String              getVersion()     {return null;}
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
index ecbcc44..b5e50f0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
@@ -31,7 +31,7 @@
 import org.apache.sis.metadata.iso.citation.Citations;
 
 // Branch-dependent imports
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -47,7 +47,7 @@
  * @since 0.3
  * @module
  */
-public final class SpecializedIdentifier<T> implements Identifier, Cloneable, Serializable {
+public final class SpecializedIdentifier<T> implements ReferenceIdentifier, Cloneable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -96,7 +96,7 @@
      *
      * @see IdentifierMapAdapter#put(Citation, String)
      */
-    static Identifier parse(final Citation authority, final String code) {
+    static ReferenceIdentifier parse(final Citation authority, final String code) {
         if (authority instanceof NonMarshalledAuthority) {
             final int ordinal = ((NonMarshalledAuthority) authority).ordinal;
             switch (ordinal) {
@@ -199,6 +199,18 @@
     }
 
     /**
+     * Returns {@code null} since this class does not hold version information.
+     *
+     * @return {@code null}.
+     *
+     * @since 0.5
+     */
+    @Override
+    public String getVersion() {
+        return null;
+    }
+
+    /**
      * Returns a hash code value for this identifier.
      */
     @Override
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java
index 398fa94..b3b7f3b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java
@@ -23,7 +23,6 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlValue;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.internal.jaxb.Context;
 
@@ -221,7 +220,7 @@
      * @param context  the current (un)marshalling context, or {@code null} if none.
      * @param code     the code list to wrap.
      */
-    public CodeListUID(final Context context, final ControlledVocabulary code) {
+    public CodeListUID(final Context context, final CodeList<?> code) {
         final String classID = Types.getListName(code);
         final String fieldID = Types.getCodeName(code);
         codeList = schema(context, classID);
@@ -238,6 +237,9 @@
             try {
                 value = ResourceBundle.getBundle("org.opengis.metadata.CodeLists",
                         locale, CodeList.class.getClassLoader()).getString(key);
+                if ("Off line access".equals(value)) {
+                    value = "Offline access";               // For having the same value than GeoAPI 3.1.
+                }
             } catch (MissingResourceException e) {
                 Context.warningOccured(context, CodeListAdapter.class, "marshal", e, false);
             }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/EnumAdapter.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/EnumAdapter.java
index 0158ec8..b0afc7d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/EnumAdapter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/EnumAdapter.java
@@ -17,10 +17,6 @@
 package org.apache.sis.internal.jaxb.cat;
 
 import javax.xml.bind.annotation.adapters.XmlAdapter;
-import org.apache.sis.util.iso.Types;
-
-// Branch-dependent imports
-import org.opengis.util.ControlledVocabulary;
 
 
 /**
@@ -87,7 +83,7 @@
      * @param  e  the enumeration constant.
      * @return the text to write in the XML element.
      */
-    protected static String value(final ControlledVocabulary e) {
-        return Types.getCodeName(e);
+    protected static String value(final Enum<?> e) {
+        return e.name();
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/CI_TelephoneTypeCode.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/CI_TelephoneTypeCode.java
index 4cfc875..3ab423e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/CI_TelephoneTypeCode.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/CI_TelephoneTypeCode.java
@@ -17,14 +17,14 @@
 package org.apache.sis.internal.jaxb.code;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.metadata.citation.TelephoneType;
-import org.apache.sis.internal.jaxb.cat.CodeListAdapter;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeListAdapter;
+import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.cat.CodeListUID;
 import org.apache.sis.xml.Namespaces;
 
 
 /**
- * JAXB adapter for {@link TelephoneType}
+ * JAXB adapter for {@code TelephoneType}
  * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
  * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
  *
@@ -34,7 +34,7 @@
  * @since   1.0
  * @module
  */
-public class CI_TelephoneTypeCode extends CodeListAdapter<CI_TelephoneTypeCode, TelephoneType> {
+public class CI_TelephoneTypeCode extends UnsupportedCodeListAdapter<CI_TelephoneTypeCode> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -64,8 +64,8 @@
      * @return the code list class.
      */
     @Override
-    protected final Class<TelephoneType> getCodeListClass() {
-        return TelephoneType.class;
+    protected String getCodeListName() {
+        return "CI_TelephoneTypeCode";
     }
 
     /**
@@ -103,7 +103,7 @@
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
         @Override protected CI_TelephoneTypeCode wrap(final CodeListUID value) {
-            return accept2014() ? super.wrap(value) : null;
+            return FilterByVersion.CURRENT_METADATA.accept() ? super.wrap(value) : null;
         }
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/DCPList.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/DCPList.java
index 6b97cc3..c4f4225 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/DCPList.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/DCPList.java
@@ -17,14 +17,13 @@
 package org.apache.sis.internal.jaxb.code;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
-import org.apache.sis.internal.jaxb.cat.CodeListAdapter;
-import org.apache.sis.internal.jaxb.cat.CodeListUID;
 import org.apache.sis.xml.Namespaces;
+import org.apache.sis.internal.jaxb.cat.CodeListUID;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeListAdapter;
 
 
 /**
- * JAXB adapter for {@link DistributedComputingPlatform}
+ * JAXB adapter for {@code DistributedComputingPlatform}
  * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
  * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
  *
@@ -33,7 +32,7 @@
  * @since   0.5
  * @module
  */
-public final class DCPList extends CodeListAdapter<DCPList, DistributedComputingPlatform> {
+public final class DCPList extends UnsupportedCodeListAdapter<DCPList> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -63,8 +62,29 @@
      * @return the code list class.
      */
     @Override
-    protected Class<DistributedComputingPlatform> getCodeListClass() {
-        return DistributedComputingPlatform.class;
+    protected String getCodeListName() {
+        return "DCPList";
+    }
+
+    /**
+     * Converts the given Java constant name to something hopefully close to the UML identifier,
+     * or close to the textual value to put in the XML.
+     *
+     * @param  name    The Java constant name (e.g. {@code WEB_SERVICES}).
+     * @param  buffer  An initially empty buffer to use for creating the identifier.
+     * @param  isValue {@code false} for the {@code codeListValue} attribute, or {@code true} for the XML value.
+     * @return The identifier (e.g. {@code "WebServices"} or {@code "Web services"}).
+     */
+    @Override
+    protected String toIdentifier(final String name, final StringBuilder buffer, final boolean isValue) {
+        if (name.startsWith("WEB_")) {
+            super.toIdentifier(name, buffer, isValue);
+            buffer.setCharAt(0, 'W');
+            return buffer.toString();
+        } else {
+            // Other names are abbreviations (e.g. XML, SQL, FTP, etc.), so return unchanged.
+            return name;
+        }
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_CharacterSetCode.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_CharacterSetCode.java
index 6962f03..6d35f4e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_CharacterSetCode.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_CharacterSetCode.java
@@ -21,6 +21,7 @@
 import java.nio.charset.IllegalCharsetNameException;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.adapters.XmlAdapter;
+import org.opengis.metadata.identification.CharacterSet;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.xml.ValueConverter;
 import org.apache.sis.internal.jaxb.Context;
@@ -105,4 +106,25 @@
     public void setElement(final CodeListUID value) {
         identifier = value;
     }
+
+    /**
+     * Converts the given Java Character Set to {@code CharacterSet}.
+     *
+     * @param  cs  the character set, or {@cod null}.
+     * @return a code list for the given character set, or {@code null} if the given {@code cs} was null.
+     */
+    public static CharacterSet fromCharset(final Charset cs) {
+        if (cs == null) {
+            return null;
+        }
+        final String name = cs.name();
+        for (final CharacterSet candidate : CharacterSet.values()) {
+            for (final String n : candidate.names()) {
+                if (name.equals(n)) {
+                    return candidate;
+                }
+            }
+        }
+        return CharacterSet.valueOf(name);
+    }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_RestrictionCode.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_RestrictionCode.java
index 5379eda..c5442db 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_RestrictionCode.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_RestrictionCode.java
@@ -94,8 +94,8 @@
      * @param  value  the unmarshalled value.
      */
     public void setElement(final CodeListUID value) {
-        if (value != null && "license".equalsIgnoreCase(value.codeListValue)) {
-            value.codeListValue = "licence";    // For matching current spelling (ISO 19115-3:2016).
+        if (value != null && "licence".equalsIgnoreCase(value.codeListValue)) {
+            value.codeListValue = "license";    // For matching legacy spelling (ISO 19139:2007).
         }
         identifier = value;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_CouplingType.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_CouplingType.java
index cb973b1..5629c0c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_CouplingType.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_CouplingType.java
@@ -17,14 +17,13 @@
 package org.apache.sis.internal.jaxb.code;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.metadata.identification.CouplingType;
-import org.apache.sis.internal.jaxb.cat.CodeListAdapter;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeListAdapter;
 import org.apache.sis.internal.jaxb.cat.CodeListUID;
 import org.apache.sis.xml.Namespaces;
 
 
 /**
- * JAXB adapter for {@link CouplingType}
+ * JAXB adapter for {@code CouplingType}
  * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
  * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
  *
@@ -33,7 +32,7 @@
  * @since   0.5
  * @module
  */
-public final class SV_CouplingType extends CodeListAdapter<SV_CouplingType, CouplingType> {
+public final class SV_CouplingType extends UnsupportedCodeListAdapter<SV_CouplingType> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -63,8 +62,8 @@
      * @return the code list class.
      */
     @Override
-    protected Class<CouplingType> getCodeListClass() {
-        return CouplingType.class;
+    protected String getCodeListName() {
+        return "SV_CouplingType";
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_ParameterDirection.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_ParameterDirection.java
deleted file mode 100644
index 28ade2b..0000000
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_ParameterDirection.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.jaxb.code;
-
-import javax.xml.bind.annotation.XmlElement;
-import org.opengis.parameter.ParameterDirection;
-import org.apache.sis.internal.jaxb.cat.EnumAdapter;
-import org.apache.sis.xml.Namespaces;
-
-
-/**
- * JAXB adapter for {@link ParameterDirection}
- * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
- * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   0.5
- * @module
- */
-public final class SV_ParameterDirection extends EnumAdapter<SV_ParameterDirection, ParameterDirection> {
-    /**
-     * The enumeration value.
-     */
-    @XmlElement(name = "SV_ParameterDirection", namespace = Namespaces.SRV)
-    private String value;
-
-    /**
-     * Empty constructor for JAXB only.
-     */
-    public SV_ParameterDirection() {
-    }
-
-    /**
-     * Returns the wrapped value.
-     *
-     * @param  wrapper  the wrapper.
-     * @return the wrapped value.
-     */
-    @Override
-    public final ParameterDirection unmarshal(final SV_ParameterDirection wrapper) {
-        return ParameterDirection.valueOf(name(wrapper.value));
-    }
-
-    /**
-     * Wraps the given value.
-     *
-     * @param  e  the value to wrap.
-     * @return the wrapped value.
-     */
-    @Override
-    public final SV_ParameterDirection marshal(final ParameterDirection e) {
-        if (e == null) {
-            return null;
-        }
-        final SV_ParameterDirection wrapper = new SV_ParameterDirection();
-        wrapper.value = value(e);
-        return wrapper;
-    }
-}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
index 8760804..d9d696b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
@@ -27,7 +27,6 @@
 import javax.xml.bind.annotation.XmlSeeAlso;
 import org.w3c.dom.Element;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.jaxb.gcx.Anchor;
@@ -259,7 +258,7 @@
         if (type != ENUM) {
             return null;
         }
-        final ControlledVocabulary code = Types.forCodeTitle(text);
+        final CodeList<?> code = Types.forCodeTitle(text);
         final String name = Types.getListName(code);
         /*
          * The namespace has have various value like CIT, SRV, MDQ, MRI, MSR, LAN, etc.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java
index 6362bc0..c64a9f9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java
@@ -18,8 +18,6 @@
 
 import java.util.Date;
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.temporal.Period;
-import org.opengis.temporal.Instant;
 import org.opengis.temporal.TemporalPrimitive;
 import org.apache.sis.internal.xml.XmlUtilities;
 import org.apache.sis.internal.jaxb.Context;
@@ -27,6 +25,10 @@
 import org.apache.sis.internal.util.TemporalUtilities;
 import org.apache.sis.util.resources.Errors;
 
+// Branch-dependent imports
+import org.apache.sis.internal.geoapi.temporal.Period;
+import org.apache.sis.internal.geoapi.temporal.Instant;
+
 
 /**
  * JAXB adapter for {@link TemporalPrimitive}, in order to integrate the value in an element complying
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
index c920d54..b1fb5b6 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
@@ -22,11 +22,13 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.datatype.XMLGregorianCalendar;
 import javax.xml.datatype.DatatypeConfigurationException;
-import org.opengis.temporal.Instant;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.internal.xml.XmlUtilities;
 
+// Branch-dependent imports
+import org.apache.sis.internal.geoapi.temporal.Instant;
+
 
 /**
  * Encapsulates a {@code gml:TimeInstant}. This element may be used alone, or included in a
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
index 71c4ff8..9c1b63a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
@@ -20,8 +20,8 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElements;
 import javax.xml.bind.annotation.XmlRootElement;
-import org.opengis.temporal.Period;
 import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.internal.geoapi.temporal.Period;
 import org.apache.sis.internal.util.Strings;
 
 import static org.apache.sis.internal.xml.LegacyNamespaces.VERSION_3_0;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriodBound.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriodBound.java
index 6774f3c..3f915d1 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriodBound.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriodBound.java
@@ -21,7 +21,9 @@
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.datatype.XMLGregorianCalendar;
-import org.opengis.temporal.Instant;
+
+// Branch-dependent imports
+import org.apache.sis.internal.geoapi.temporal.Instant;
 
 
 /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_Duration.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_Duration.java
index ba2d4e7..e80f683 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_Duration.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_Duration.java
@@ -17,7 +17,7 @@
 package org.apache.sis.internal.jaxb.gts;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.temporal.Duration;
+import javax.xml.datatype.Duration;
 import org.opengis.temporal.PeriodDuration;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
@@ -99,6 +99,6 @@
      * @param  duration  the value to set.
      */
     public void setElement(final javax.xml.datatype.Duration duration) {
-        metadata = TM_PeriodDuration.toISO(duration);
+        metadata = /*TM_PeriodDuration.toISO*/(duration);
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java
index da8bd38..9f338e4 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java
@@ -23,7 +23,7 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.datatype.DatatypeConfigurationException;
 import org.opengis.temporal.PeriodDuration;
-import org.opengis.temporal.TemporalFactory;
+import org.apache.sis.internal.geoapi.temporal.TemporalFactory;
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.xml.XmlUtilities;
@@ -96,8 +96,10 @@
     /**
      * Converts the given ISO 19108 duration into a Java XML duration.
      */
-    static Duration toXML(final PeriodDuration metadata) {
-        if (metadata != null) try {
+    static Duration toXML(final PeriodDuration duration) {
+        if (duration instanceof org.apache.sis.internal.geoapi.temporal.PeriodDuration) try {
+            final org.apache.sis.internal.geoapi.temporal.PeriodDuration metadata =
+                    (org.apache.sis.internal.geoapi.temporal.PeriodDuration) duration;
             /*
              * Get the DatatypeFactory first because if not available, then we don't need to parse
              * the calendar fields. This has the side effect of not validating the calendar fields
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Party.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Party.java
index e7f6042..32db806 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Party.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Party.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.citation.Party;
 import org.apache.sis.metadata.iso.citation.AbstractParty;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public final class CI_Party extends PropertyType<CI_Party, Party> {
+public final class CI_Party extends PropertyType<CI_Party, AbstractParty> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code Party.class}
      */
     @Override
-    protected Class<Party> getBoundType() {
-        return Party.class;
+    protected Class<AbstractParty> getBoundType() {
+        return AbstractParty.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CI_Party(final Party value) {
+    private CI_Party(final AbstractParty value) {
         super(value);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected CI_Party wrap(final Party value) {
+    protected CI_Party wrap(final AbstractParty value) {
         return new CI_Party(value);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public AbstractParty getElement() {
-        return AbstractParty.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Responsibility.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Responsibility.java
index 4de669b..319d598 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Responsibility.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Responsibility.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty;
@@ -35,7 +34,7 @@
  * @since   0.5
  * @module
  */
-public final class CI_Responsibility extends PropertyType<CI_Responsibility, Responsibility> {
+public final class CI_Responsibility extends PropertyType<CI_Responsibility, DefaultResponsibility> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -43,21 +42,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code Responsibility.class}
      */
     @Override
-    protected Class<Responsibility> getBoundType() {
-        return Responsibility.class;
+    protected Class<DefaultResponsibility> getBoundType() {
+        return DefaultResponsibility.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CI_Responsibility(final Responsibility value) {
+    private CI_Responsibility(final DefaultResponsibility value) {
         super(value);
     }
 
@@ -69,7 +66,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected CI_Responsibility wrap(final Responsibility value) {
+    protected CI_Responsibility wrap(final DefaultResponsibility value) {
         return new CI_Responsibility(value);
     }
 
@@ -88,9 +85,13 @@
                 // Need to build new DefaultResponsibility object here — simply casting doesn't work.
                 return new DefaultResponsibility(metadata);
             }
-            return DefaultResponsibility.castOrCopy(metadata);
+            return metadata;
         } else if (FilterByVersion.LEGACY_METADATA.accept()) {
-            return DefaultResponsibleParty.castOrCopy(metadata);
+            if (metadata instanceof DefaultResponsibleParty) {
+                return metadata;
+            } else {
+                return new DefaultResponsibleParty(metadata);
+            }
         } else {
             return null;
         }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AssociatedResource.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AssociatedResource.java
deleted file mode 100644
index cb59754..0000000
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AssociatedResource.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.jaxb.metadata;
-
-import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.AssociatedResource;
-import org.apache.sis.metadata.iso.identification.DefaultAssociatedResource;
-import org.apache.sis.internal.jaxb.gco.PropertyType;
-
-
-/**
- * JAXB adapter mapping implementing class to the GeoAPI interface. See
- * package documentation for more information about JAXB and interface.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
- * @since   0.5
- * @module
- */
-public final class MD_AssociatedResource extends PropertyType<MD_AssociatedResource, AssociatedResource> {
-    /**
-     * Empty constructor for JAXB only.
-     */
-    public MD_AssociatedResource() {
-    }
-
-    /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
-     *
-     * @return {@code AssociatedResource.class}
-     */
-    @Override
-    protected Class<AssociatedResource> getBoundType() {
-        return AssociatedResource.class;
-    }
-
-    /**
-     * Constructor for the {@link #wrap} method only.
-     */
-    private MD_AssociatedResource(final AssociatedResource value) {
-        super(value);
-    }
-
-    /**
-     * Invoked by {@link PropertyType} at marshalling time for wrapping the given metadata value
-     * in a {@code <mri:MD_AssociatedResource>} XML element.
-     *
-     * @param  value  the metadata element to marshal.
-     * @return a {@code PropertyType} wrapping the given the metadata element.
-     */
-    @Override
-    protected MD_AssociatedResource wrap(final AssociatedResource value) {
-        return new MD_AssociatedResource(value);
-    }
-
-    /**
-     * Invoked by JAXB at marshalling time for getting the actual metadata to write
-     * inside the {@code <mri:MD_AssociatedResource>} XML element.
-     * This is the value or a copy of the value given in argument to the {@code wrap} method.
-     *
-     * @return the metadata to be marshalled.
-     */
-    @XmlElementRef
-    public DefaultAssociatedResource getElement() {
-        return DefaultAssociatedResource.castOrCopy(metadata);
-    }
-
-    /**
-     * Invoked by JAXB at unmarshalling time for storing the result temporarily.
-     *
-     * @param  value  the unmarshalled metadata.
-     */
-    public void setElement(final DefaultAssociatedResource value) {
-        metadata = value;
-    }
-}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AttributeGroup.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AttributeGroup.java
index 0f2a149..0a20155 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AttributeGroup.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AttributeGroup.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.content.AttributeGroup;
 import org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public final class MD_AttributeGroup extends PropertyType<MD_AttributeGroup, AttributeGroup> {
+public final class MD_AttributeGroup extends PropertyType<MD_AttributeGroup, DefaultAttributeGroup> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code AttributeGroup.class}
      */
     @Override
-    protected Class<AttributeGroup> getBoundType() {
-        return AttributeGroup.class;
+    protected Class<DefaultAttributeGroup> getBoundType() {
+        return DefaultAttributeGroup.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_AttributeGroup(final AttributeGroup value) {
+    private MD_AttributeGroup(final DefaultAttributeGroup value) {
         super(value);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_AttributeGroup wrap(final AttributeGroup value) {
+    protected MD_AttributeGroup wrap(final DefaultAttributeGroup value) {
         return new MD_AttributeGroup(value);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public DefaultAttributeGroup getElement() {
-        return DefaultAttributeGroup.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_FeatureTypeInfo.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_FeatureTypeInfo.java
index 6faa790..38cb76c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_FeatureTypeInfo.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_FeatureTypeInfo.java
@@ -20,7 +20,6 @@
 
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 import org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo;
-import org.opengis.metadata.content.FeatureTypeInfo;
 
 
 /**
@@ -32,7 +31,7 @@
  * @version 1.0
  * @module
  */
-public final class MD_FeatureTypeInfo extends PropertyType<MD_FeatureTypeInfo, FeatureTypeInfo> {
+public final class MD_FeatureTypeInfo extends PropertyType<MD_FeatureTypeInfo, DefaultFeatureTypeInfo> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -47,14 +46,14 @@
      * @return {@code FeatureTypeInfo.class}
      */
     @Override
-    protected Class<FeatureTypeInfo> getBoundType() {
-        return FeatureTypeInfo.class;
+    protected Class<DefaultFeatureTypeInfo> getBoundType() {
+        return DefaultFeatureTypeInfo.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_FeatureTypeInfo(final FeatureTypeInfo value) {
+    private MD_FeatureTypeInfo(final DefaultFeatureTypeInfo value) {
         super(value);
     }
 
@@ -66,7 +65,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_FeatureTypeInfo wrap(final FeatureTypeInfo value) {
+    protected MD_FeatureTypeInfo wrap(final DefaultFeatureTypeInfo value) {
         return new MD_FeatureTypeInfo(value);
     }
 
@@ -79,7 +78,7 @@
      */
     @XmlElementRef
     public DefaultFeatureTypeInfo getElement() {
-        return DefaultFeatureTypeInfo.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Identifier.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Identifier.java
index a94754a..fe39cdd 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Identifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Identifier.java
@@ -18,6 +18,7 @@
 
 import javax.xml.bind.annotation.XmlElementRef;
 import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
@@ -81,14 +82,15 @@
      */
     @XmlElementRef
     public final DefaultIdentifier getElement() {
-        if (FilterByVersion.LEGACY_METADATA.accept() && metadata != null) {
+        if (FilterByVersion.LEGACY_METADATA.accept() && metadata instanceof ReferenceIdentifier) {
             /*
              * In legacy specification, "code space" and "version" were not defined in <gmd:MD_Identifier> but were
              * defined in <gmd:RS_Identifier> subclass. In newer specification there is no longer such special case.
              * Note that "description" did not existed anywhere in legacy specification.
              */
-            if (metadata.getCodeSpace() != null || metadata.getVersion() != null) {
-                return RS_Identifier.wrap(metadata);
+            final ReferenceIdentifier id = (ReferenceIdentifier) metadata;
+            if (id.getCodeSpace() != null || id.getVersion() != null) {
+                return RS_Identifier.wrap(id);
             }
         }
         return DefaultIdentifier.castOrCopy(metadata);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_KeywordClass.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_KeywordClass.java
index abafc74..2c45152 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_KeywordClass.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_KeywordClass.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.KeywordClass;
 import org.apache.sis.metadata.iso.identification.DefaultKeywordClass;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public class MD_KeywordClass extends PropertyType<MD_KeywordClass, KeywordClass> {
+public class MD_KeywordClass extends PropertyType<MD_KeywordClass, DefaultKeywordClass> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -46,14 +45,14 @@
      * @return {@code KeywordClass.class}
      */
     @Override
-    protected final Class<KeywordClass> getBoundType() {
-        return KeywordClass.class;
+    protected final Class<DefaultKeywordClass> getBoundType() {
+        return DefaultKeywordClass.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_KeywordClass(final KeywordClass value) {
+    private MD_KeywordClass(final DefaultKeywordClass value) {
         super(value);
     }
 
@@ -65,7 +64,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_KeywordClass wrap(final KeywordClass value) {
+    protected MD_KeywordClass wrap(final DefaultKeywordClass value) {
         return new MD_KeywordClass(value);
     }
 
@@ -78,7 +77,7 @@
      */
     @XmlElementRef
     public final DefaultKeywordClass getElement() {
-        return DefaultKeywordClass.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
@@ -104,7 +103,7 @@
          *
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
-        @Override protected MD_KeywordClass wrap(final KeywordClass value) {
+        @Override protected MD_KeywordClass wrap(final DefaultKeywordClass value) {
             return accept2014() ? super.wrap(value) : null;
         }
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_MetadataScope.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_MetadataScope.java
index 5bfaea6..2ac07f8 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_MetadataScope.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_MetadataScope.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.MetadataScope;
 import org.apache.sis.metadata.iso.DefaultMetadataScope;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public final class MD_MetadataScope extends PropertyType<MD_MetadataScope, MetadataScope> {
+public final class MD_MetadataScope extends PropertyType<MD_MetadataScope, DefaultMetadataScope> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code MetadataScope.class}
      */
     @Override
-    protected Class<MetadataScope> getBoundType() {
-        return MetadataScope.class;
+    protected Class<DefaultMetadataScope> getBoundType() {
+        return DefaultMetadataScope.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_MetadataScope(final MetadataScope value) {
+    private MD_MetadataScope(final DefaultMetadataScope value) {
         super(value);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_MetadataScope wrap(final MetadataScope value) {
+    protected MD_MetadataScope wrap(final DefaultMetadataScope value) {
         return new MD_MetadataScope(value);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public DefaultMetadataScope getElement() {
-        return DefaultMetadataScope.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Releasability.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Releasability.java
index 2d240e8..724f296 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Releasability.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Releasability.java
@@ -19,7 +19,6 @@
 import javax.xml.bind.annotation.XmlElementRef;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 import org.apache.sis.metadata.iso.constraint.DefaultReleasability;
-import org.opengis.metadata.constraint.Releasability;
 
 
 /**
@@ -32,7 +31,7 @@
  * @version 1.0
  * @module
  */
-public class MD_Releasability extends PropertyType<MD_Releasability, Releasability> {
+public class MD_Releasability extends PropertyType<MD_Releasability, DefaultReleasability> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -47,14 +46,14 @@
      * @return {@code Releasability.class}
      */
     @Override
-    protected final Class<Releasability> getBoundType() {
-        return Releasability.class;
+    protected final Class<DefaultReleasability> getBoundType() {
+        return DefaultReleasability.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_Releasability(final Releasability value) {
+    private MD_Releasability(final DefaultReleasability value) {
         super(value);
     }
 
@@ -66,7 +65,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_Releasability wrap(final Releasability value) {
+    protected MD_Releasability wrap(final DefaultReleasability value) {
         return new MD_Releasability(value);
     }
 
@@ -79,7 +78,7 @@
      */
     @XmlElementRef
     public final DefaultReleasability getElement() {
-        return DefaultReleasability.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
@@ -105,7 +104,7 @@
          *
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
-        @Override protected MD_Releasability wrap(final Releasability value) {
+        @Override protected MD_Releasability wrap(final DefaultReleasability value) {
             return accept2014() ? super.wrap(value) : null;
         }
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Scope.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Scope.java
index 65f78ea..3421484 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Scope.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Scope.java
@@ -18,7 +18,7 @@
 
 import java.net.URISyntaxException;
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.apache.sis.metadata.iso.maintenance.DefaultScope;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -103,7 +103,7 @@
 
         /** Converts an adapter read from an XML stream. */
         @Override public Scope unmarshal(final MD_Scope value) throws URISyntaxException {
-            return org.apache.sis.metadata.iso.quality.DefaultScope.castOrCopy(super.unmarshal(value));
+            return DefaultScope.castOrCopy(super.unmarshal(value));
         }
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_CoupledResource.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_CoupledResource.java
index ddcc040..b876539 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_CoupledResource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_CoupledResource.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.CoupledResource;
 import org.apache.sis.metadata.iso.identification.DefaultCoupledResource;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public final class SV_CoupledResource extends PropertyType<SV_CoupledResource, CoupledResource> {
+public final class SV_CoupledResource extends PropertyType<SV_CoupledResource, DefaultCoupledResource> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code CoupledResource.class}
      */
     @Override
-    protected Class<CoupledResource> getBoundType() {
-        return CoupledResource.class;
+    protected Class<DefaultCoupledResource> getBoundType() {
+        return DefaultCoupledResource.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private SV_CoupledResource(final CoupledResource value) {
+    private SV_CoupledResource(final DefaultCoupledResource value) {
         super(value);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected SV_CoupledResource wrap(final CoupledResource value) {
+    protected SV_CoupledResource wrap(final DefaultCoupledResource value) {
         return new SV_CoupledResource(value);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public DefaultCoupledResource getElement() {
-        return DefaultCoupledResource.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationChainMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationChainMetadata.java
index 94b6ad2..017382f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationChainMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationChainMetadata.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.OperationChainMetadata;
 import org.apache.sis.metadata.iso.identification.DefaultOperationChainMetadata;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public final class SV_OperationChainMetadata extends PropertyType<SV_OperationChainMetadata, OperationChainMetadata> {
+public final class SV_OperationChainMetadata extends PropertyType<SV_OperationChainMetadata, DefaultOperationChainMetadata> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code OperationChainMetadata.class}
      */
     @Override
-    protected Class<OperationChainMetadata> getBoundType() {
-        return OperationChainMetadata.class;
+    protected Class<DefaultOperationChainMetadata> getBoundType() {
+        return DefaultOperationChainMetadata.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private SV_OperationChainMetadata(final OperationChainMetadata value) {
+    private SV_OperationChainMetadata(final DefaultOperationChainMetadata value) {
         super(value);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected SV_OperationChainMetadata wrap(final OperationChainMetadata value) {
+    protected SV_OperationChainMetadata wrap(final DefaultOperationChainMetadata value) {
         return new SV_OperationChainMetadata(value);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public DefaultOperationChainMetadata getElement() {
-        return DefaultOperationChainMetadata.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationMetadata.java
index 5a07a58..5420abf 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationMetadata.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.OperationMetadata;
 import org.apache.sis.metadata.iso.identification.DefaultOperationMetadata;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public class SV_OperationMetadata extends PropertyType<SV_OperationMetadata, OperationMetadata> {
+public class SV_OperationMetadata extends PropertyType<SV_OperationMetadata, DefaultOperationMetadata> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code OperationMetadata.class}
      */
     @Override
-    protected final Class<OperationMetadata> getBoundType() {
-        return OperationMetadata.class;
+    protected final Class<DefaultOperationMetadata> getBoundType() {
+        return DefaultOperationMetadata.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private SV_OperationMetadata(final OperationMetadata value) {
+    private SV_OperationMetadata(final DefaultOperationMetadata value) {
         super(value);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected SV_OperationMetadata wrap(final OperationMetadata value) {
+    protected SV_OperationMetadata wrap(final DefaultOperationMetadata value) {
         return new SV_OperationMetadata(value);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public final DefaultOperationMetadata getElement() {
-        return DefaultOperationMetadata.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
@@ -104,7 +101,7 @@
          *
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
-        @Override protected SV_OperationMetadata wrap(final OperationMetadata value) {
+        @Override protected SV_OperationMetadata wrap(final DefaultOperationMetadata value) {
             return accept2014() ? super.wrap(value) : null;
         }
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameter.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameter.java
index 8b190ed..6d9d652 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameter.java
@@ -16,18 +16,19 @@
  */
 package org.apache.sis.internal.jaxb.metadata.replace;
 
+import java.util.Set;
 import java.util.Objects;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.measure.Unit;
 import org.opengis.util.TypeName;
 import org.opengis.util.MemberName;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterValue;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.internal.simple.SimpleIdentifiedObject;
@@ -42,7 +43,6 @@
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.resources.Errors;
 
-import static org.apache.sis.util.Utilities.deepEquals;
 import static org.apache.sis.internal.util.CollectionsExt.nonNull;
 
 
@@ -73,7 +73,6 @@
 @XmlType(name = "SV_Parameter_Type", namespace = Namespaces.SRV, propOrder = {
     "memberName",           // The  ISO 19115-3:2016 way to marshal name.
     "legacyName",           // Legacy ISO 19139:2007 way to marshal name.
-    "direction",
     "description",
     "optionality",
     "optionalityLabel",     // Legacy ISO 19139:2007 way to marshal optionality.
@@ -111,12 +110,6 @@
     MemberName memberName;
 
     /**
-     * Indication if the parameter is an input to the service, an output or both.
-     */
-    @XmlElement(required = true)
-    ParameterDirection direction;
-
-    /**
      * A narrative explanation of the role of the parameter.
      */
     @XmlElement
@@ -163,8 +156,6 @@
     private ServiceParameter(final ParameterDescriptor<?> parameter) {
         super(parameter);
         memberName    = getMemberName(parameter);
-        direction     = parameter.getDirection();
-        description   = parameter.getDescription();
         optionality   = parameter.getMinimumOccurs() > 0;
         repeatability = parameter.getMaximumOccurs() > 1;
     }
@@ -192,7 +183,7 @@
      */
     public static MemberName getMemberName(final ParameterDescriptor<?> parameter) {
         if (parameter != null) {
-            final Identifier id = parameter.getName();
+            final ReferenceIdentifier id = parameter.getName();
             if (id instanceof MemberName) {
                 return (MemberName) id;
             }
@@ -304,21 +295,10 @@
     }
 
     /**
-     * Returns an indication if the parameter is an input to the service, an output or both.
-     *
-     * @return indication if the parameter is an input or output to the service, or {@code null} if unspecified.
-     */
-    @Override
-    public ParameterDirection getDirection() {
-        return direction;
-    }
-
-    /**
      * Returns a narrative explanation of the role of the parameter.
      *
      * @return a narrative explanation of the role of the parameter, or {@code null} if none.
      */
-    @Override
     public InternationalString getDescription() {
         return description;
     }
@@ -377,6 +357,16 @@
     }
 
     /**
+     * Optional properties.
+     * @return {@code null}.
+     */
+    @Override public Set<?>        getValidValues()  {return null;}     // Really null, not an empty set. See method contract.
+    @Override public Comparable<?> getMinimumValue() {return null;}
+    @Override public Comparable<?> getMaximumValue() {return null;}
+    @Override public Object        getDefaultValue() {return null;}
+    @Override public Unit<?>       getUnit()         {return null;}
+
+    /**
      * Creates a new instance of {@code ParameterValue}.
      * This method delegates the work to {@link org.apache.sis.parameter.DefaultParameterDescriptor}
      * since this {@code ServiceParameter} class is not a full-featured parameter descriptor implementation.
@@ -417,13 +407,11 @@
                     return Objects.equals(toString(getName()), toString(that.getName()));
                     // super.equals(…) already compared 'getName()' in others mode.
                 }
-                return deepEquals(that.getDescription(), getDescription(), mode) &&
-                                  that.getDirection()     == getDirection()     &&
-                                  that.getMinimumOccurs() == getMinimumOccurs() &&
-                                  that.getMaximumOccurs() == getMaximumOccurs() &&
-                                  that.getValidValues()   == null &&
-                                  that.getMinimumValue()  == null &&
-                                  that.getMaximumValue()  == null;
+                return that.getMinimumOccurs() == getMinimumOccurs() &&
+                       that.getMaximumOccurs() == getMaximumOccurs() &&
+                       that.getValidValues()   == null &&
+                       that.getMinimumValue()  == null &&
+                       that.getMaximumValue()  == null;
             }
         }
         return false;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java
index 9e56492..9753a89 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java
@@ -45,7 +45,6 @@
 @XmlJavaTypeAdapters({
     @XmlJavaTypeAdapter(GO_Boolean.class),
     @XmlJavaTypeAdapter(MD_Identifier.class),
-    @XmlJavaTypeAdapter(SV_ParameterDirection.class),
 
     // Java types, primitive types and basic OGC types handling
     @XmlJavaTypeAdapter(StringAdapter.class),
@@ -63,6 +62,5 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.jaxb.metadata.MD_Identifier;
-import org.apache.sis.internal.jaxb.code.SV_ParameterDirection;
 import org.apache.sis.internal.jaxb.gco.*;
 import org.apache.sis.xml.Namespaces;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Identifiers.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Identifiers.java
index c764dee..303c8e2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Identifiers.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Identifiers.java
@@ -19,6 +19,7 @@
 import java.util.Locale;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.CollectionsExt;
@@ -96,7 +97,7 @@
      * </ul>
      *
      * Use {@code toCodeSpace(…)} method when assigning values to be returned by methods like
-     * {@link Identifier#getCodeSpace()}, since those values are likely to be compared without special
+     * {@link ReferenceIdentifier#getCodeSpace()}, since those values are likely to be compared without special
      * care about ignorable identifier characters. But if the intent is to format a more complex string
      * like WKT or {@code toString()}, then we suggest to use {@code getIdentifier(citation, true)} instead,
      * which will produce the same result but preserving the ignorable characters, which can be useful
@@ -123,7 +124,10 @@
                          * Unicode identifiers. If a codespace exists, then the code does not need to begin
                          * with a "Unicode identifier start" (it may be a "Unicode identifier part").
                          */
-                        String cs = CharSequences.trimWhitespaces(id.getCodeSpace());
+                        String cs = null;
+                        if (id instanceof ReferenceIdentifier) {
+                            cs = CharSequences.trimWhitespaces(((ReferenceIdentifier) id).getCodeSpace());
+                        }
                         if (cs == null || cs.isEmpty()) {
                             cs = null;
                             isUnicode = CharSequences.isUnicodeIdentifier(candidate);
@@ -217,8 +221,8 @@
                 return Citations.identifierMatches(authority, other);
             }
         }
-        if (codeSpace != null) {
-            final String other = identifier.getCodeSpace();
+        if (codeSpace != null && identifier instanceof ReferenceIdentifier) {
+            final String other = ((ReferenceIdentifier) identifier).getCodeSpace();
             if (other != null) {
                 return CharSequences.equalsFiltered(codeSpace, other, Characters.Filter.UNICODE_IDENTIFIER, true);
             }
@@ -241,12 +245,12 @@
      * @return {@code TRUE} or {@code FALSE} on match or mismatch respectively, or {@code null} if this method
      *         can not determine if there is a match or mismatch.
      */
-    public static Boolean hasCommonIdentifier(final Iterable<? extends Identifier> id1,
-                                              final Iterable<? extends Identifier> id2)
+    public static Boolean hasCommonIdentifier(final Iterable<? extends ReferenceIdentifier> id1,
+                                              final Iterable<? extends ReferenceIdentifier> id2)
     {
         if (id1 != null && id2 != null) {
             boolean hasFound = false;
-            for (final Identifier identifier : id1) {
+            for (final ReferenceIdentifier identifier : id1) {
                 final Citation authority = identifier.getAuthority();
                 final String   codeSpace = identifier.getCodeSpace();
                 for (final Identifier other : id2) {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameToIdentifier.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameToIdentifier.java
index b8112d7..f1979c5 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameToIdentifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameToIdentifier.java
@@ -126,6 +126,14 @@
     }
 
     /**
+     * Returns {@code null} since names are not versioned.
+     */
+    @Override
+    public String getVersion() {
+        return null;
+    }
+
+    /**
      * Returns a hash code value for this object.
      */
     @Override
@@ -199,7 +207,7 @@
      *
      * @since 0.8
      */
-    public static boolean isHeuristicMatchForIdentifier(final Iterable<? extends Identifier> identifiers, final String toSearch) {
+    public static boolean isHeuristicMatchForIdentifier(final Iterable<? extends ReferenceIdentifier> identifiers, final String toSearch) {
         if (toSearch != null && identifiers != null) {
             int s = toSearch.indexOf(DefaultNameSpace.DEFAULT_SEPARATOR);
             if (s < 0) {
@@ -214,7 +222,7 @@
             do {
                 final String codespace = toSearch.substring(0, s).trim();
                 final String code = toSearch.substring(++s).trim();
-                for (final Identifier id : identifiers) {
+                for (final ReferenceIdentifier id : identifiers) {
                     if (codespace.equalsIgnoreCase(id.getCodeSpace()) && code.equalsIgnoreCase(id.getCode())) {
                         return true;
                     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java
index c3638cf..6bdf322 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java
@@ -19,10 +19,10 @@
 import java.text.Format;
 import java.util.Locale;
 import java.util.TimeZone;
-import java.util.function.Supplier;
 import javax.sql.DataSource;
 import java.sql.SQLException;
-import org.opengis.util.ControlledVocabulary;
+import java.util.function.Supplier;
+import org.opengis.util.CodeList;
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.internal.util.MetadataServices;
 import org.apache.sis.internal.metadata.sql.Initializer;
@@ -70,10 +70,10 @@
      * @param  locale  desired locale for the title.
      * @return the title.
      *
-     * @see org.apache.sis.util.iso.Types#getCodeTitle(ControlledVocabulary)
+     * @see org.apache.sis.util.iso.Types#getCodeTitle(CodeList)
      */
     @Override
-    public String getCodeTitle(final ControlledVocabulary code, final Locale locale) {
+    public String getCodeTitle(final CodeList<?> code, final Locale locale) {
         return Types.getCodeTitle(code).toString(locale);
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
index a2de591..01d4ed2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
@@ -208,8 +208,6 @@
     @Override public Collection<PresentationForm>               getPresentationForms()       {return delegate().getPresentationForms();}
     @Override public Series                                     getSeries()                  {return delegate().getSeries();}
     @Override public InternationalString                        getOtherCitationDetails()    {return delegate().getOtherCitationDetails();}
-    @Override public Collection<? extends OnlineResource>       getOnlineResources()         {return delegate().getOnlineResources();}
-    @Override public Collection<? extends BrowseGraphic>        getGraphics()                {return delegate().getGraphics();}
     @Override public String                                     getISBN()                    {return delegate().getISBN();}
     @Override public String                                     getISSN()                    {return delegate().getISSN();}
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleAttributeType.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleAttributeType.java
index 674e725..a3e83cd 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleAttributeType.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleAttributeType.java
@@ -18,8 +18,6 @@
 
 import java.io.Serializable;
 import org.opengis.util.Type;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.util.TypeName;
@@ -28,7 +26,7 @@
 /**
  * A simple attribute type containing only a name and a class of values.
  * Such simple type are suitable for use in ISO 19103 {@link org.opengis.util.RecordType}
- * in addition to ISO 19109 {@link org.opengis.feature.FeatureType}.
+ * in addition to ISO 19109 {@code org.opengis.feature.FeatureType}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.5
@@ -38,7 +36,7 @@
  * @since 0.5
  * @module
  */
-public final class SimpleAttributeType<V> implements AttributeType<V>, Type, Serializable {
+public final class SimpleAttributeType<V> implements Type, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -70,7 +68,6 @@
      *
      * @return the name of this attribute type.
      */
-    @Override
     public GenericName getName() {
         return name;
     }
@@ -90,7 +87,6 @@
      *
      * @return the class of value for attributes of this type.
      */
-    @Override
     public Class<V> getValueClass() {
         return valueClass;
     }
@@ -100,7 +96,6 @@
      *
      * @return always 1.
      */
-    @Override
     public int getMinimumOccurs() {
         return 1;
     }
@@ -110,7 +105,6 @@
      *
      * @return always 1.
      */
-    @Override
     public int getMaximumOccurs() {
         return 1;
     }
@@ -120,7 +114,6 @@
      *
      * @return always {@code null}.
      */
-    @Override
     public V getDefaultValue() {
         return null;
     }
@@ -130,17 +123,26 @@
      *
      * @return always {@code null}.
      */
-    @Override
     public InternationalString getDefinition() {
         return null;
     }
 
     /**
-     * Unsupported operation.
+     * Not used for this simple attribute type.
+     *
+     * @return always {@code null}.
      */
-    @Override
-    public Attribute<V> newInstance() {
-        throw new UnsupportedOperationException();
+    public InternationalString getDesignation() {
+        return null;
+    }
+
+    /**
+     * Not used for this simple attribute type.
+     *
+     * @return always {@code null}.
+     */
+    public InternationalString getDescription() {
+        return null;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleCitation.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleCitation.java
index 76ecb6b..87f74ff 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleCitation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleCitation.java
@@ -16,9 +16,17 @@
  */
 package org.apache.sis.internal.simple;
 
+import java.util.Date;
 import java.util.Objects;
+import java.util.Collection;
+import java.util.Collections;
 import java.io.Serializable;
+import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.citation.CitationDate;
+import org.opengis.metadata.citation.PresentationForm;
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.opengis.metadata.citation.Series;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.internal.util.Strings;
@@ -69,6 +77,26 @@
     }
 
     /**
+     * Methods inherited from the {@link Citation} interface which are not of interest to this
+     * {@code SimpleCitation} implementation.
+     *
+     * @return an empty list.
+     */
+    @Override public Collection<? extends InternationalString>  getAlternateTitles()         {return Collections.emptyList();}
+    @Override public Collection<? extends CitationDate>         getDates()                   {return Collections.emptyList();}
+    @Override public InternationalString                        getEdition()                 {return null;}
+    @Override public Date                                       getEditionDate()             {return null;}
+    @Override public Collection<? extends Identifier>           getIdentifiers()             {return Collections.emptyList();}
+    @Override public Collection<? extends ResponsibleParty>     getCitedResponsibleParties() {return Collections.emptyList();}
+    @Override public Collection<PresentationForm>               getPresentationForms()       {return Collections.emptyList();}
+    @Override public Series                                     getSeries()                  {return null;}
+    @Override public InternationalString                        getOtherCitationDetails()    {return null;}
+    @Override public String                                     getISBN()                    {return null;}
+    @Override public String                                     getISSN()                    {return null;}
+    @Deprecated
+    @Override public InternationalString                        getCollectiveTitle()         {return null;}
+
+    /**
      * Compares the given object with this citation for equality.
      *
      * @param  obj  the object to compare with this citation.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleDuration.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleDuration.java
index 909a3f5..d634a41 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleDuration.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleDuration.java
@@ -17,7 +17,7 @@
 package org.apache.sis.internal.simple;
 
 import org.apache.sis.internal.util.Numerics;
-import org.opengis.temporal.Duration;
+import org.apache.sis.internal.geoapi.temporal.Duration;
 
 
 /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleExtent.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleExtent.java
index 7724694..34aad4f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleExtent.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleExtent.java
@@ -21,6 +21,7 @@
 import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.metadata.extent.TemporalExtent;
 import org.opengis.metadata.extent.VerticalExtent;
+import org.opengis.util.InternationalString;
 
 import static org.apache.sis.internal.util.CollectionsExt.singletonOrEmpty;
 
@@ -67,6 +68,7 @@
         this.temporalElements   = temporalElements;
     }
 
+    @Override public InternationalString          getDescription()        {return null;}
     @Override public Collection<GeographicExtent> getGeographicElements() {return singletonOrEmpty(geographicElements);}
     @Override public Collection<VerticalExtent>   getVerticalElements()   {return singletonOrEmpty(verticalElements);}
     @Override public Collection<TemporalExtent>   getTemporalElements()   {return singletonOrEmpty(temporalElements);}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java
index 056611d..bfcff3d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java
@@ -19,7 +19,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.distribution.Distributor;
 import org.opengis.metadata.distribution.Format;
 import org.apache.sis.internal.util.Strings;
 
@@ -68,13 +68,68 @@
     }
 
     /**
-     * Citation / URL of the specification format.
+     * @deprecated Replaced by {@link #getTitle()}
      *
-     * @return citation / URL of the specification format.
+     * @return name of a subset, profile, or product specification of the format, or {@code null}.
      */
     @Override
-    public Citation getFormatSpecificationCitation() {
-        return this;
+    @Deprecated
+    public InternationalString getSpecification() {
+        return getTitle();
+    }
+
+    /**
+     * @deprecated Replaced by {@link #getAlternateTitles()}
+     *
+     * @return name of the data transfer format(s).
+     */
+    @Override
+    @Deprecated
+    public InternationalString getName() {
+        return super.getTitle();
+    }
+
+    /**
+     * @deprecated Replaced by {@link #getEdition()}
+     *
+     * @return version of the format.
+     */
+    @Override
+    @Deprecated
+    public InternationalString getVersion() {
+        return getEdition();
+    }
+
+    /**
+     * Amendment number of the format version.
+     *
+     * @return amendment number of the format version, or {@code null}.
+     */
+    @Override
+    public InternationalString getAmendmentNumber() {
+        return null;
+    }
+
+    /**
+     * Recommendations of algorithms or processes that can be applied to read
+     * or expand resources to which compression techniques have been applied.
+     *
+     * @return processes that can be applied to read resources to which compression techniques have been applied,
+     *         or {@code null}.
+     */
+    @Override
+    public InternationalString getFileDecompressionTechnique() {
+        return null;
+    }
+
+    /**
+     * Provides information about the distributor's format.
+     *
+     * @return information about the distributor's format.
+     */
+    @Override
+    public Collection<? extends Distributor> getFormatDistributors() {
+        return Collections.emptyList();
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifiedObject.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifiedObject.java
index c2efe00..b302f5d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifiedObject.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifiedObject.java
@@ -16,8 +16,12 @@
  */
 package org.apache.sis.internal.simple;
 
+import java.util.Set;
 import java.util.Objects;
+import java.util.Collection;
+import java.util.Collections;
 import java.io.Serializable;
+import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
@@ -91,6 +95,34 @@
     }
 
     /**
+     * Method required by the {@link IdentifiedObject} interface.
+     * Current implementation returns an empty set.
+     *
+     * <p>If a future version allows this method to returns a non-empty set,
+     * revisit {@link #equals(Object, ComparisonMode)}.</p>
+     *
+     * @return the identifiers, or an empty set if none.
+     */
+    @Override
+    public final Set<ReferenceIdentifier> getIdentifiers() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * Method required by the {@link IdentifiedObject} interface.
+     * Current implementation returns an empty set.
+     *
+     * <p>If a future version allows this method to returns a non-empty set,
+     * revisit {@link #equals(Object, ComparisonMode)}.</p>
+     *
+     * @return the aliases, or an empty set if none.
+     */
+    @Override
+    public final Collection<GenericName> getAlias() {
+        return Collections.emptySet();
+    }
+
+    /**
      * Method required by most {@link IdentifiedObject} sub-interfaces.
      * Current implementation returns {@code null}.
      *
@@ -117,6 +149,20 @@
     }
 
     /**
+     * Method required by the {@link IdentifiedObject} interface.
+     * Current implementation returns {@code null}.
+     *
+     * <p>If a future version allows this method to returns a non-null value,
+     * revisit {@link #equals(Object, ComparisonMode)}.</p>
+     *
+     * @return the remarks, or {@code null} if none.
+     */
+    @Override
+    public final InternationalString getRemarks() {
+        return null;
+    }
+
+    /**
      * Returns a hash code value for this object.
      */
     @Override
@@ -171,13 +217,24 @@
     }
 
     /**
+     * Throws an exception in all cases, since this object can't be formatted in a valid WKT.
+     *
+     * @return the Well Known Text.
+     * @throws UnsupportedOperationException always thrown.
+     */
+    @Override
+    public String toWKT() throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Returns a pseudo-WKT representation for debugging purpose.
      */
     @Override
     public String toString() {
         final String code, codespace;
         final Citation authority;
-        final Identifier name = this.name;
+        final ReferenceIdentifier name = this.name;
         if (name != null) {
             code      = name.getCode();
             codespace = name.getCodeSpace();
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifier.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifier.java
index ae65239..7ffd8d1 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifier.java
@@ -138,6 +138,17 @@
     }
 
     /**
+     * Returns a natural language description of the meaning of the code value.
+     *
+     * @return natural language description, or {@code null} if none.
+     *
+     * @since 0.5
+     */
+    public InternationalString getDescription() {
+        return null;
+    }
+
+    /**
      * An optional free text.
      *
      * @since 0.6
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java
index 523cb51..d36086b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java
@@ -16,21 +16,33 @@
  */
 package org.apache.sis.internal.simple;
 
-import java.nio.charset.Charset;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.Locale;
-import java.util.Map;
+import org.opengis.metadata.ApplicationSchemaInformation;
+import org.opengis.metadata.Identifier;
 import org.opengis.metadata.Metadata;
-import org.opengis.metadata.MetadataScope;
+import org.opengis.metadata.MetadataExtensionInformation;
+import org.opengis.metadata.PortrayalCatalogueReference;
+import org.opengis.metadata.acquisition.AcquisitionInformation;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.CitationDate;
 import org.opengis.metadata.citation.PresentationForm;
 import org.opengis.metadata.citation.ResponsibleParty;
+import org.opengis.metadata.citation.Series;
+import org.opengis.metadata.constraint.Constraints;
+import org.opengis.metadata.content.ContentInformation;
+import org.opengis.metadata.distribution.Distribution;
+import org.opengis.metadata.distribution.Format;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.identification.*;
+import org.opengis.metadata.maintenance.MaintenanceInformation;
 import org.opengis.metadata.maintenance.ScopeCode;
+import org.opengis.metadata.quality.DataQuality;
+import org.opengis.metadata.spatial.SpatialRepresentation;
 import org.opengis.metadata.spatial.SpatialRepresentationType;
+import org.opengis.referencing.ReferenceSystem;
 import org.opengis.util.InternationalString;
 
 
@@ -43,8 +55,8 @@
  * <p>Unless specified otherwise, all methods in this class returns {@code null} or an empty collection by default.
  * The exceptions to this rules are the following methods:</p>
  * <ul>
- *   <li>{@link #getMetadataScopes()} returns {@code this}</li>
- *   <li>{@link #getResourceScope()} returns {@link ScopeCode#DATASET}</li>
+ *   <li>{@code  getMetadataScopes()} returns {@code this}</li>
+ *   <li>{@code  getResourceScope()} returns {@link ScopeCode#DATASET}</li>
  *   <li>{@link #getIdentificationInfo()} returns {@code this}</li>
  *   <li>{@link #getCitation()} returns {@code this}</li>
  *   <li>{@link #getSpatialRepresentationTypes()} returns {@link SpatialRepresentationType#VECTOR}</li>
@@ -62,11 +74,11 @@
  * </ul>
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 0.8
  * @since   0.8
  * @module
  */
-public class SimpleMetadata implements Metadata, MetadataScope, DataIdentification, Citation {
+public class SimpleMetadata implements Metadata, DataIdentification, Citation {
     /**
      * Creates a new metadata object.
      */
@@ -74,34 +86,82 @@
     }
 
     /**
+     * Unique identifier for this metadata record.
+     */
+    @Override
+    public String getFileIdentifier() {
+        return null;
+    }
+
+    /**
      * Language(s) used for documenting metadata.
      * Also the language(s) used within the data.
      */
     @Override
-    public Map<Locale,Charset> getLocalesAndCharsets() {
-        return Collections.emptyMap();
+    public Collection<Locale> getLanguages() {
+        return Collections.emptySet();                  // We use 'Set' because we handle 'Locale' like a CodeList.
     }
 
     /**
-     * The scope or type of resource for which metadata is provided.
-     * This method returns {@code this} for allowing call to {@link #getResourceScope()}.
-     *
-     * @see #getResourceScope()
-     * @see #getName()
+     * Language(s) used for documenting metadata.
+     * Also the language(s) used within the data.
      */
     @Override
-    public Collection<MetadataScope> getMetadataScopes() {
-        return Collections.singleton(this);
+    public Locale getLanguage() {
+        return null;
     }
 
     /**
-     * Code for the metadata scope, fixed to {@link ScopeCode#DATASET} by default. This is part of the information
-     * provided by {@link #getMetadataScopes()}. The {@code DATASET} default value is consistent with the fact that
+     * Language(s) used for documenting metadata.
+     * Also the language(s) used within the data.
+     */
+    @Override
+    public Collection<Locale> getLocales() {
+        return Collections.emptySet();                  // We use 'Set' because we handle 'Locale' like a CodeList.
+    }
+
+    /**
+     * The character coding standard used for the metadata set.
+     * Also the character coding standard(s) used for the dataset.
+     */
+    @Override
+    public Collection<CharacterSet> getCharacterSets() {
+        return Collections.emptySet();                  // We use 'Set' because we handle 'Charset' like a CodeList.
+    }
+
+    /**
+     * The character coding standard used for the metadata set.
+     * Also the character coding standard(s) used for the dataset.
+     */
+    @Override
+    public CharacterSet getCharacterSet() {
+        return null;
+    }
+
+    /**
+     * Identification of the parent metadata record.
+     */
+    @Override
+    public String getParentIdentifier() {
+        return null;
+    }
+
+    /**
+     * Code for the metadata scope, fixed to {@link ScopeCode#DATASET} by default.
+     * The {@code DATASET} default value is consistent with the fact that
      * {@code SimpleMetadata} implements {@link DataIdentification}.
      */
     @Override
-    public ScopeCode getResourceScope() {
-        return ScopeCode.DATASET;
+    public Collection<ScopeCode> getHierarchyLevels() {
+        return Collections.singleton(ScopeCode.DATASET);
+    }
+
+    /**
+     * Description of the metadata scope.
+     */
+    @Override
+    public Collection<String> getHierarchyLevelNames() {
+        return Collections.emptySet();
     }
 
     /**
@@ -116,7 +176,56 @@
      * Date(s) associated with the metadata.
      */
     @Override
-    public Collection<CitationDate> getDateInfo() {
+    public Date getDateStamp() {
+        return null;
+    }
+
+    /**
+     * Citation(s) for the standard(s) to which the metadata conform.
+     */
+    @Override
+    public String getMetadataStandardName() {
+        return null;
+    }
+
+    /**
+     * As of ISO 19115:2014, replaced by {@code getMetadataStandards()}
+     * followed by {@link Citation#getEdition()}.
+     */
+    @Override
+    public String getMetadataStandardVersion() {
+        return null;
+    }
+
+    /**
+     * Online location(s) where the metadata is available.
+     */
+    @Override
+    public String getDataSetUri() {
+        return null;
+    }
+
+    /**
+     * Digital representation of spatial information in the dataset.
+     */
+    @Override
+    public Collection<SpatialRepresentation> getSpatialRepresentationInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Description of the spatial and temporal reference systems used in the dataset.
+     */
+    @Override
+    public Collection<ReferenceSystem> getReferenceSystemInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information describing metadata extensions.
+     */
+    @Override
+    public Collection<MetadataExtensionInformation> getMetadataExtensionInfo() {
         return Collections.emptyList();
     }
 
@@ -130,7 +239,6 @@
      * @see #getPointOfContacts()
      * @see #getSpatialRepresentationTypes()
      * @see #getSpatialResolutions()
-     * @see #getTemporalResolutions()
      * @see #getTopicCategories()
      * @see #getExtents()
      * @see #getResourceFormats()
@@ -141,6 +249,70 @@
         return Collections.singleton(this);
     }
 
+    /**
+     * Information about the feature and coverage characteristics.
+     */
+    @Override
+    public Collection<ContentInformation> getContentInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the distributor of and options for obtaining the resource(s).
+     */
+    @Override
+    public Distribution getDistributionInfo() {
+        return null;
+    }
+
+    /**
+     * Overall assessment of quality of a resource(s).
+     */
+    @Override
+    public Collection<DataQuality> getDataQualityInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the catalogue of rules defined for the portrayal of a resource(s).
+     */
+    @Override
+    public Collection<PortrayalCatalogueReference> getPortrayalCatalogueInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Restrictions on the access and use of metadata.
+     */
+    @Override
+    public Collection<Constraints> getMetadataConstraints() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the conceptual schema of a dataset.
+     */
+    @Override
+    public Collection<ApplicationSchemaInformation> getApplicationSchemaInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the acquisition of the data.
+     */
+    @Override
+    public Collection<AcquisitionInformation> getAcquisitionInformation() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the frequency of metadata updates, and the scope of those updates.
+     */
+    @Override
+    public MaintenanceInformation getMetadataMaintenance() {
+        return null;
+    }
+
 
     /* -------------------------------------------------------------------------------------------------
      * Implementation of the DataIdentification object returned by Metadata.getIdentificationInfo().
@@ -166,6 +338,42 @@
     }
 
     /**
+     * Summary of the intentions with which the resource was developed.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public InternationalString getPurpose() {
+        return null;
+    }
+
+    /**
+     * Recognition of those who contributed to the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<String> getCredits() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Status of the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Progress> getStatus() {
+        return Collections.emptySet();              // We use 'Set' because 'Progress' is a CodeList.
+    }
+
+    /**
+     * Identification of, and means of communication with, person(s) and organisations associated with the resource(s).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<ResponsibleParty> getPointOfContacts() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Methods used to spatially represent geographic information.
      * This is part of the information returned by {@link #getIdentificationInfo()}.
      * Default implementation returns {@link SpatialRepresentationType#VECTOR}.
@@ -177,6 +385,15 @@
     }
 
     /**
+     * Factor which provides a general understanding of the density of spatial data in the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Resolution> getSpatialResolutions() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Main theme(s) of the resource.
      * This is part of the information returned by {@link #getIdentificationInfo()}.
      * Default implementation returns {@link TopicCategory#LOCATION}.
@@ -196,6 +413,89 @@
         return Collections.emptyList();
     }
 
+    /**
+     * Information about the frequency of resource updates, and the scope of those updates.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<MaintenanceInformation> getResourceMaintenances() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Graphic that illustrates the resource(s) (should include a legend for the graphic).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<BrowseGraphic> getGraphicOverviews() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Description of the format of the resource(s).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Format> getResourceFormats() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Category keywords, their type, and reference source.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Keywords> getDescriptiveKeywords() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Basic information about specific application(s) for which the resource(s)
+     * has/have been or is being used by different users.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Usage> getResourceSpecificUsages() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about constraints which apply to the resource(s).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Constraints> getResourceConstraints() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * @deprecated As of ISO 19115:2014, replaced by {@code getAssociatedResources()}.
+     */
+    @Override
+    @Deprecated
+    public Collection<AggregateInformation> getAggregationInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Description of the resource in the producer's processing environment, including items
+     * such as the software, the computer operating system, file name, and the dataset size.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public InternationalString getEnvironmentDescription() {
+        return null;
+    }
+
+    /**
+     * Any other descriptive information about the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public InternationalString getSupplementalInformation() {
+        return null;
+    }
+
 
     /* -------------------------------------------------------------------------------------------------
      * Implementation of the Citation object returned by DataIdentification.getCitation().
@@ -211,6 +511,61 @@
     }
 
     /**
+     * Short names or other language names by which the cited information is known.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<InternationalString> getAlternateTitles() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Reference dates for the cited resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<CitationDate> getDates() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Version of the cited resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public InternationalString getEdition() {
+        return null;
+    }
+
+    /**
+     * Date of the edition.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Date getEditionDate() {
+        return null;
+    }
+
+    /**
+     * Unique identifier for the resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<Identifier> getIdentifiers() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Role, name, contact and position information for individuals or organisations
+     * that are responsible for the resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<ResponsibleParty> getCitedResponsibleParties() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Mode in which the resource is represented.
      * This is part of the information returned by {@link #getCitation()}.
      * Default implementation returns {@link PresentationForm#TABLE_DIGITAL}.
@@ -220,4 +575,49 @@
     public Collection<PresentationForm> getPresentationForms() {
         return Collections.singleton(PresentationForm.TABLE_DIGITAL);
     }
+
+    /**
+     * Information about the series, or aggregate dataset, of which the dataset is a part.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Series getSeries() {
+        return null;
+    }
+
+    /**
+     * Other information required to complete the citation that is not recorded elsewhere.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public InternationalString getOtherCitationDetails() {
+        return null;
+    }
+
+    /**
+     * @deprecated Removed as of ISO 19115:2014.
+     */
+    @Override
+    @Deprecated
+    public InternationalString getCollectiveTitle() {
+        return null;
+    }
+
+    /**
+     * International Standard Book Number.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public String getISBN() {
+        return null;
+    }
+
+    /**
+     * International Standard Serial Number.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public String getISSN() {
+        return null;
+    }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
index 3ac23f9..bf2f1bc 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
@@ -30,6 +30,7 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.ExtendedElementInformation;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.collection.TreeTable;
@@ -113,7 +114,7 @@
      * than GeoAPI, but have a slight performance cost at construction time. Performance
      * after construction should be the same.</p>
      */
-    static final boolean IMPLEMENTATION_CAN_ALTER_API = false;
+    static final boolean IMPLEMENTATION_CAN_ALTER_API = true;
 
     /**
      * Metadata instances defined in this class. The current implementation does not yet
@@ -136,15 +137,16 @@
 
     /**
      * An instance working on ISO 19123 standard as defined by GeoAPI interfaces
-     * in the {@link org.opengis.coverage} package and sub-packages.
+     * in the {@code org.opengis.coverage} package and sub-packages.
      */
     public static final MetadataStandard ISO_19123;
     static {
+        final String[] prefix = {"Default", "Abstract"};
         final String[] acronyms = {"CoordinateSystem", "CS", "CoordinateReferenceSystem", "CRS"};
 
         // If new StandardImplementation instances are added below, please update StandardImplementation.readResolve().
-        ISO_19115 = new StandardImplementation("ISO 19115", "org.opengis.metadata.", "org.apache.sis.metadata.iso.", null, null);
-        ISO_19111 = new StandardImplementation("ISO 19111", "org.opengis.referencing.", "org.apache.sis.referencing.", acronyms, new MetadataStandard[] {ISO_19115});
+        ISO_19115 = new StandardImplementation("ISO 19115", "org.opengis.metadata.", "org.apache.sis.metadata.iso.", prefix, null, null);
+        ISO_19111 = new StandardImplementation("ISO 19111", "org.opengis.referencing.", "org.apache.sis.referencing.", prefix, acronyms, new MetadataStandard[] {ISO_19115});
         ISO_19123 = new MetadataStandard      ("ISO 19123", "org.opengis.coverage.", new MetadataStandard[] {ISO_19111});
         INSTANCES = new MetadataStandard[] {
             ISO_19111,
@@ -742,10 +744,10 @@
      * <p>In the particular case of Apache SIS implementation, all values in the information map
      * additionally implement the following interfaces:</p>
      * <ul>
-     *   <li>{@link Identifier} with the following properties:
+     *   <li>{@link ReferenceIdentifier} with the following properties:
      *     <ul>
      *       <li>The {@linkplain Identifier#getAuthority() authority} is this metadata standard {@linkplain #getCitation() citation}.</li>
-     *       <li>The {@linkplain Identifier#getCodeSpace() codespace} is the standard name of the interface that contain the property.</li>
+     *       <li>The {@linkplain ReferenceIdentifier#getCodeSpace() codespace} is the standard name of the interface that contain the property.</li>
      *       <li>The {@linkplain Identifier#getCode() code} is the standard name of the property.</li>
      *     </ul>
      *   </li>
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
index d7f80ad..9b647d2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
@@ -24,6 +24,7 @@
 
 import org.opengis.annotation.UML;
 import org.opengis.annotation.Obligation;
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -128,8 +129,14 @@
                      * the parent class, and we want the properties in the parent class to be sorted first.
                      * If duplicated properties are found, keep the first occurence (i.e. sort the property
                      * with the most specialized child that declared it).
+                     *
+                     * We make an exception for ResponsibleParty.role, which should be replaced by Party.role
+                     * but this replacement is not yet effective in GeoAPI 3.0.
                      */
-                    order.putIfAbsent(propOrder[i], order.size());
+                    final String prop = propOrder[i];
+                    if (!"role".equals(prop) || !ResponsibleParty.class.isAssignableFrom(implementation)) {
+                        order.putIfAbsent(prop, order.size());
+                    }
                 }
             }
             implementation = implementation.getSuperclass();
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java
index b0faf00..13737b1 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java
@@ -183,6 +183,30 @@
     }
 
     /**
+     * Unconditionally returns {@code null}.
+     *
+     * @deprecated This property was defined in the 2003 edition of ISO 19115,
+     *             but has been removed in the 2014 edition.
+     */
+    @Override
+    @Deprecated
+    public String getShortName() {
+        return null;
+    }
+
+    /**
+     * Unconditionally returns {@code null}.
+     *
+     * @deprecated This property was defined in the 2003 edition of ISO 19115,
+     *             but has been removed in the 2014 edition.
+     */
+    @Override
+    @Deprecated
+    public Integer getDomainCode() {
+        return null;
+    }
+
+    /**
      * Returns the definition of this property, or {@code null} if none.
      */
     @Override
@@ -304,6 +328,22 @@
     }
 
     /**
+     * Unconditionally returns {@code null}.
+     */
+    public InternationalString getRationale() {
+        return null;
+    }
+
+    /**
+     * Unconditionally returns an empty list.
+     */
+    @Override
+    @Deprecated
+    public Collection<InternationalString> getRationales() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Returns the name of the person or organization creating the element.
      */
     @Override
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
index 0340dda..a3965b0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
@@ -18,7 +18,7 @@
 
 import java.util.Iterator;
 import java.util.Collection;
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 import org.apache.sis.util.Emptiable;
 import org.apache.sis.internal.util.CollectionsExt;
 
@@ -135,7 +135,7 @@
                 } else if (!prune && element instanceof Emptiable) {
                     isEmptyElement = ((Emptiable) element).isEmpty();
                     // If 'prune' is true, we will rather test for Emptiable after our pruning attempt.
-                } else if (!(element instanceof ControlledVocabulary)) {
+                } else if (!(element instanceof Enum<?>) && !(element instanceof CodeList<?>)) {
                     final MetadataStandard standard = MetadataStandard.forClass(element.getClass());
                     if (standard != null) {
                         /*
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
index b54389b..6cd2ece 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
@@ -20,8 +20,6 @@
 import java.util.IdentityHashMap;
 import java.io.ObjectStreamException;
 import org.opengis.annotation.UML;
-import org.opengis.annotation.Classifier;
-import org.opengis.annotation.Stereotype;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.internal.system.Modules;
@@ -50,6 +48,12 @@
     private final String implementationPackage;
 
     /**
+     * The prefixes that implementation classes may have.
+     * The most common prefixes should be first, since the prefixes will be tried in that order.
+     */
+    private final String[] prefix;
+
+    /**
      * The acronyms that implementation classes may have, or {@code null} if none. If non-null,
      * then this array shall contain (<var>full text</var>, <var>acronym</var>) pairs. The full
      * text shall appear to the end of the class name, otherwise it is not replaced. This is
@@ -77,30 +81,21 @@
      * @param citation               the title of the standard.
      * @param interfacePackage       the root package for metadata interfaces, with a trailing {@code '.'}.
      * @param implementationPackage  the root package for metadata implementations. with a trailing {@code '.'}.
+     * @param prefix                 the prefix of implementation class. This array is not cloned.
      * @param acronyms               an array of (full text, acronyms) pairs. This array is not cloned.
      * @param dependencies           the dependencies to other metadata standards, or {@code null} if none.
      */
     StandardImplementation(final String citation, final String interfacePackage, final String implementationPackage,
-            final String[] acronyms, final MetadataStandard[] dependencies)
+            final String[] prefix, final String[] acronyms, final MetadataStandard[] dependencies)
     {
         super(citation, interfacePackage, dependencies);
         this.implementationPackage = implementationPackage;
+        this.prefix                = prefix;
         this.acronyms              = acronyms;
         this.implementations       = new IdentityHashMap<>();
     }
 
     /**
-     * Returns {@code true} if the given type is conceptually abstract.
-     * The given type is usually an interface, so here "abstract" can not be in the Java sense.
-     * If this method can not find information about whether the given type is abstract,
-     * then this method conservatively returns {@code false}.
-     */
-    private static boolean isAbstract(final Class<?> type) {
-        final Classifier c = type.getAnnotation(Classifier.class);
-        return (c != null) && c.value() == Stereotype.ABSTRACT;
-    }
-
-    /**
      * Accepts Apache SIS implementation classes as "pseudo-interfaces" if they are annotated with {@link UML}.
      * We use this feature for example in the transition from ISO 19115:2003 to ISO 19115:2014, when new API is
      * defined in Apache SIS but not yet available in GeoAPI interfaces.
@@ -150,17 +145,22 @@
                         }
                     }
                     /*
-                     * Try to instantiate the implementation class.
+                     * Try to insert a prefix in front of the class name, until a match is found.
                      */
                     final int prefixPosition = buffer.lastIndexOf(".") + 1;
-                    buffer.insert(prefixPosition, isAbstract(type) ? "Abstract" : "Default");
-                    classname = buffer.toString();
-                    try {
-                        candidate = Class.forName(classname);
+                    int length = 0;
+                    for (final String p : prefix) {
+                        classname = buffer.replace(prefixPosition, prefixPosition + length, p).toString();
+                        try {
+                            candidate = Class.forName(classname);
+                        } catch (ClassNotFoundException e) {
+                            Logging.recoverableException(getLogger(Modules.METADATA),
+                                    MetadataStandard.class, "getImplementation", e);
+                            length = p.length();
+                            continue;
+                        }
                         implementations.put(type, candidate);
                         return candidate.asSubclass(type);
-                    } catch (ClassNotFoundException e) {
-                        Logging.recoverableException(getLogger(Modules.METADATA), MetadataStandard.class, "getImplementation", e);
                     }
                     implementations.put(type, Void.TYPE);                       // Marker for "class not found".
                 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
index 2177eef..aabcbdf 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
@@ -16,9 +16,7 @@
  */
 package org.apache.sis.metadata.iso;
 
-import java.util.AbstractSet;
 import java.util.Collection;
-import java.util.Iterator;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
@@ -41,6 +39,11 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * New metadata element, not found in ISO 19115, which is required to describe geographic data.
@@ -543,8 +546,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "rationale")
+    @UML(identifier="rationale", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getRationale() {
         return LegacyPropertyAdapter.getSingleton(rationales, InternationalString.class, null,
                 DefaultExtendedElementInformation.class, "getRationale");
@@ -570,29 +573,7 @@
     @Deprecated
     @Dependencies("getRationale")
     public Collection<InternationalString> getRationales() {
-        return new AbstractSet<InternationalString>() {
-            /** Returns 0 if empty, or 1 if a density has been specified. */
-            @Override public int size() {
-                return getRationale() != null ? 1 : 0;
-            }
-
-            /** Returns an iterator over 0 or 1 element. Current iterator implementation is unmodifiable. */
-            @Override public Iterator<InternationalString> iterator() {
-                return CollectionsExt.singletonOrEmpty(getRationale()).iterator();
-            }
-
-            /** Adds an element only if the set is empty. This method is invoked by JAXB at unmarshalling time. */
-            @Override public boolean add(final InternationalString newValue) {
-                if (isEmpty()) {
-                    setRationale(newValue);
-                    return true;
-                } else {
-                    LegacyPropertyAdapter.warnIgnoredExtraneous(InternationalString.class,
-                            DefaultExtendedElementInformation.class, "setRationales");
-                    return false;
-                }
-            }
-        };
+        return rationales = nonNullCollection(rationales, InternationalString.class);
     }
 
     /**
@@ -602,8 +583,7 @@
      */
     @Deprecated
     public void setRationales(final Collection<? extends InternationalString> newValues) {
-        setRationale(LegacyPropertyAdapter.getSingleton(newValues, InternationalString.class,
-                null, DefaultExtendedElementInformation.class, "setRationales"));
+        rationales = writeCollection(newValues, rationales, InternationalString.class);
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultIdentifier.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultIdentifier.java
index 443556d..29f963f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultIdentifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultIdentifier.java
@@ -22,11 +22,17 @@
 import javax.xml.bind.annotation.XmlSeeAlso;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.util.InternationalString;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Value uniquely identifying an object within a namespace.
@@ -217,10 +223,17 @@
         super(object);
         if (object != null) {
             code        = object.getCode();
-            codeSpace   = object.getCodeSpace();
-            version     = object.getVersion();
-            description = object.getDescription();
             authority   = object.getAuthority();
+            if (object instanceof DefaultIdentifier) {
+                final DefaultIdentifier c = (DefaultIdentifier) object;
+                codeSpace   = c.getCodeSpace();
+                version     = c.getVersion();
+                description = c.getDescription();
+            } else if (object instanceof ReferenceIdentifier) {
+                final ReferenceIdentifier c = (ReferenceIdentifier) object;
+                codeSpace = c.getCodeSpace();
+                version   = c.getVersion();
+            }
         }
     }
 
@@ -310,8 +323,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "codeSpace")
+    @UML(identifier="codeSpace", obligation=OPTIONAL, specification=ISO_19115)
     public String getCodeSpace() {
         return codeSpace;
     }
@@ -337,8 +350,8 @@
      *
      * @return the version identifier for the namespace, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "version")
+    @UML(identifier="version", obligation=OPTIONAL, specification=ISO_19115)
     public String getVersion() {
         return version;
     }
@@ -362,8 +375,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "description")
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
index 3a21772..39b986c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
@@ -21,6 +21,7 @@
 import java.util.Set;
 import java.util.EnumSet;
 import java.util.Map;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -36,7 +37,6 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.Metadata;
-import org.opengis.metadata.MetadataScope;
 import org.opengis.metadata.ApplicationSchemaInformation;
 import org.opengis.metadata.MetadataExtensionInformation;
 import org.opengis.metadata.PortrayalCatalogueReference;
@@ -85,6 +85,14 @@
 import org.apache.sis.internal.converter.SurjectiveConverter;
 import org.apache.sis.math.FunctionProperty;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.internal.jaxb.code.MD_CharacterSetCode;
+
 
 /**
  * Root entity which defines metadata about a resource or resources.
@@ -217,7 +225,7 @@
     /**
      * Scope to which the metadata applies.
      */
-    private Collection<MetadataScope> metadataScopes;
+    private Collection<DefaultMetadataScope> metadataScopes;
 
     /**
      * Parties responsible for the metadata information.
@@ -329,8 +337,8 @@
      * @param identificationInfo  basic information about the resource to which the metadata applies.
      */
     public DefaultMetadata(final ResponsibleParty contact,
-                           final Date           dateStamp,
-                           final Identification identificationInfo)
+                           final Date             dateStamp,
+                           final Identification   identificationInfo)
     {
         this.contacts  = singleton(contact, ResponsibleParty.class);
         this.identificationInfo = singleton(identificationInfo, Identification.class);
@@ -351,16 +359,7 @@
     public DefaultMetadata(final Metadata object) {
         super(object);
         if (object != null) {
-            identifiers                   = singleton(object.getMetadataIdentifier(), Identifier.class);
-            parentMetadata                = object.getParentMetadata();
-            locales                       = copyMap       (object.getLocalesAndCharsets(),            Locale.class);
-            metadataScopes                = copyCollection(object.getMetadataScopes(),                MetadataScope.class);
             contacts                      = copyCollection(object.getContacts(),                      ResponsibleParty.class);
-            dateInfo                      = copyCollection(object.getDateInfo(),                      CitationDate.class);
-            metadataStandards             = copyCollection(object.getMetadataStandards(),             Citation.class);
-            metadataProfiles              = copyCollection(object.getMetadataProfiles(),              Citation.class);
-            alternativeMetadataReferences = copyCollection(object.getAlternativeMetadataReferences(), Citation.class);
-            metadataLinkages              = copyCollection(object.getMetadataLinkages(),              OnlineResource.class);
             spatialRepresentationInfo     = copyCollection(object.getSpatialRepresentationInfo(),     SpatialRepresentation.class);
             referenceSystemInfo           = copyCollection(object.getReferenceSystemInfo(),           ReferenceSystem.class);
             metadataExtensionInfo         = copyCollection(object.getMetadataExtensionInfo(),         MetadataExtensionInformation.class);
@@ -373,7 +372,35 @@
             applicationSchemaInfo         = copyCollection(object.getApplicationSchemaInfo(),         ApplicationSchemaInformation.class);
             metadataMaintenance           = object.getMetadataMaintenance();
             acquisitionInformation        = copyCollection(object.getAcquisitionInformation(),        AcquisitionInformation.class);
-            resourceLineages              = copyCollection(object.getResourceLineages(),              Lineage.class);
+            if (object instanceof DefaultMetadata) {
+                final DefaultMetadata c = (DefaultMetadata) object;
+                identifiers                   = singleton(c.getMetadataIdentifier(), Identifier.class);
+                parentMetadata                = c.getParentMetadata();
+                locales                       = copyMap       (c.getLocalesAndCharsets(),            Locale.class);
+                metadataScopes                = copyCollection(c.getMetadataScopes(),                DefaultMetadataScope.class);
+                dateInfo                      = copyCollection(c.getDateInfo(),                      CitationDate.class);
+                metadataStandards             = copyCollection(c.getMetadataStandards(),             Citation.class);
+                metadataProfiles              = copyCollection(c.getMetadataProfiles(),              Citation.class);
+                alternativeMetadataReferences = copyCollection(c.getAlternativeMetadataReferences(), Citation.class);
+                metadataLinkages              = copyCollection(c.getMetadataLinkages(),              OnlineResource.class);
+                resourceLineages              = copyCollection(c.getResourceLineages(),              Lineage.class);
+            } else {
+                setFileIdentifier         (object.getFileIdentifier());
+                setParentIdentifier       (object.getParentIdentifier());
+                setLanguage               (object.getLanguage());
+                setLocales                (object.getLocales());
+                setCharacterSet           (object.getCharacterSet());
+                setHierarchyLevels        (object.getHierarchyLevels());
+                setHierarchyLevelNames    (object.getHierarchyLevelNames());
+                setDateStamp              (object.getDateStamp());
+                setMetadataStandardName   (object.getMetadataStandardName());
+                setMetadataStandardVersion(object.getMetadataStandardVersion());
+                try {
+                    setDataSetUri(object.getDataSetUri());
+                } catch (URISyntaxException e) {
+                    throw new IllegalArgumentException(e);
+                }
+            }
         }
     }
 
@@ -454,9 +481,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "metadataIdentifier")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="metadataIdentifier", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getMetadataIdentifier() {
         return super.getIdentifier();
     }
@@ -533,8 +560,8 @@
      *
      * @since 1.0
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="defaultLocale+otherLocale", obligation=CONDITIONAL, specification=ISO_19115)
     public Map<Locale,Charset> getLocalesAndCharsets() {
         return locales = nonNullMap(locales, Locale.class);
     }
@@ -651,6 +678,27 @@
     }
 
     /**
+     * Sets information about an alternatively used localized character string for a linguistic extension.
+     *
+     * @param  newValues  the new locales.
+     *
+     * @deprecated Replaced by putting keys in {@link #getLocalesAndCharsets()}.
+     */
+    @Deprecated
+    public void setLocales(final Collection<? extends Locale> newValues) {
+        final Collection<Locale> legacy = getLocales();
+        if (legacy != null) {
+            legacy.addAll(newValues);
+        } else if (!newValues.isEmpty()) {
+            final Map<Locale,Charset> locales = new LinkedHashMap<>();
+            for (final Locale locale : newValues) {
+                locales.put(locale, null);
+            }
+            setLocalesAndCharsets(locales);
+        }
+    }
+
+    /**
      * Converter from {@link PT_Locale} and {@link Locale}.
      */
     private static final class ToLocale extends SurjectiveConverter<PT_Locale,Locale> {
@@ -718,7 +766,7 @@
     @Dependencies("getLocalesAndCharsets")
     // @XmlElement at the end of this class.
     public CharacterSet getCharacterSet() {
-        return CharacterSet.fromCharset(LegacyPropertyAdapter.getSingleton(getCharacterSets(),
+        return MD_CharacterSetCode.fromCharset(LegacyPropertyAdapter.getSingleton(getCharacterSets(),
                 Charset.class, null, DefaultMetadata.class, "getCharacterSet"));
     }
 
@@ -742,9 +790,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "parentMetadata")
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="parentMetadata", obligation=CONDITIONAL, specification=ISO_19115)
     public Citation getParentMetadata() {
         return parentMetadata;
     }
@@ -811,25 +859,35 @@
     /**
      * Returns the scope or type of resource for which metadata is provided.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code MetadataScope} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return scope or type of resource for which metadata is provided.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<MetadataScope> getMetadataScopes() {
-        return metadataScopes = nonNullCollection(metadataScopes, MetadataScope.class);
+    @UML(identifier="metadataScope", obligation=CONDITIONAL, specification=ISO_19115)
+    public Collection<DefaultMetadataScope> getMetadataScopes() {
+        return metadataScopes = nonNullCollection(metadataScopes, DefaultMetadataScope.class);
     }
 
     /**
      * Sets the scope or type of resource for which metadata is provided.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code MetadataScope} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new scope or type of resource.
      *
      * @since 0.5
      */
-    public void setMetadataScopes(final Collection<? extends MetadataScope> newValues) {
-        metadataScopes = writeCollection(newValues, metadataScopes, MetadataScope.class);
+    public void setMetadataScopes(final Collection<? extends DefaultMetadataScope> newValues) {
+        metadataScopes = writeCollection(newValues, metadataScopes, DefaultMetadataScope.class);
     }
 
     /**
@@ -848,22 +906,19 @@
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
         return new MetadataScopeAdapter<ScopeCode>(getMetadataScopes()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected MetadataScope wrap(final ScopeCode value) {
+            @Override protected DefaultMetadataScope wrap(final ScopeCode value) {
                 return new DefaultMetadataScope(value, null);
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected ScopeCode unwrap(final MetadataScope container) {
+            @Override protected ScopeCode unwrap(final DefaultMetadataScope container) {
                 return container.getResourceScope();
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final MetadataScope container, final ScopeCode value) {
-                if (container instanceof DefaultMetadataScope) {
-                    ((DefaultMetadataScope) container).setResourceScope(value);
-                    return true;
-                }
-                return false;
+            @Override protected boolean update(final DefaultMetadataScope container, final ScopeCode value) {
+                container.setResourceScope(value);
+                return true;
             }
         }.validOrNull();
     }
@@ -898,23 +953,20 @@
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
         return new MetadataScopeAdapter<String>(getMetadataScopes()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected MetadataScope wrap(final String value) {
+            @Override protected DefaultMetadataScope wrap(final String value) {
                 return new DefaultMetadataScope(null, value);
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected String unwrap(final MetadataScope container) {
+            @Override protected String unwrap(final DefaultMetadataScope container) {
                 final InternationalString name = container.getName();
                 return (name != null) ? name.toString() : null;
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final MetadataScope container, final String value) {
-                if (container instanceof DefaultMetadataScope) {
-                    ((DefaultMetadataScope) container).setName(value != null ? new SimpleInternationalString(value) : null);
-                    return true;
-                }
-                return false;
+            @Override protected boolean update(final DefaultMetadataScope container, final String value) {
+                container.setName(value != null ? new SimpleInternationalString(value) : null);
+                return true;
             }
         }.validOrNull();
     }
@@ -967,8 +1019,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="dateInfo", obligation=MANDATORY, specification=ISO_19115)
     public Collection<CitationDate> getDateInfo() {
         return dateInfo = nonNullCollection(dateInfo, CitationDate.class);
     }
@@ -1059,8 +1111,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="metadataStandard", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getMetadataStandards() {
         return metadataStandards = nonNullCollection(metadataStandards, Citation.class);
     }
@@ -1087,8 +1139,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="metadataProfile", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getMetadataProfiles() {
         return metadataProfiles = nonNullCollection(metadataProfiles, Citation.class);
     }
@@ -1112,8 +1164,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="alternativeMetadataReference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAlternativeMetadataReferences() {
         return alternativeMetadataReferences = nonNullCollection(alternativeMetadataReferences, Citation.class);
     }
@@ -1239,8 +1291,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="metadataLinkage", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getMetadataLinkages() {
         return metadataLinkages = nonNullCollection(metadataLinkages, OnlineResource.class);
     }
@@ -1274,8 +1326,8 @@
         if (FilterByVersion.LEGACY_METADATA.accept() && (info = getIdentificationInfo()) != null) {
             for (final Identification identification : info) {
                 final Citation citation = identification.getCitation();
-                if (citation != null) {
-                    final Collection<? extends OnlineResource> onlineResources = citation.getOnlineResources();
+                if (citation instanceof DefaultCitation) {
+                    final Collection<? extends OnlineResource> onlineResources = ((DefaultCitation) citation).getOnlineResources();
                     if (onlineResources != null) {
                         for (final OnlineResource link : onlineResources) {
                             final URI uri = link.getLinkage();
@@ -1605,8 +1657,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="resourceLineage", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Lineage> getResourceLineages() {
         return resourceLineages = nonNullCollection(resourceLineages, Lineage.class);
     }
@@ -1732,7 +1784,7 @@
     }
 
     @XmlElement(name = "metadataScope")
-    private Collection<MetadataScope> getMetadataScope() {
+    private Collection<DefaultMetadataScope> getMetadataScope() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getMetadataScopes() : null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java
index 55d169e..88ef371 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java
@@ -20,10 +20,15 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.MetadataScope;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the scope of the resource.
@@ -32,6 +37,14 @@
  * <div class="preformat">{@code MD_MetadataScope}
  * {@code   └─resourceScope……} Resource scope</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code MetadataScope} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -52,7 +65,8 @@
     "name"
 })
 @XmlRootElement(name = "MD_MetadataScope")
-public class DefaultMetadataScope extends ISOMetadata implements MetadataScope {
+@UML(identifier="MD_MetadataScope", specification=ISO_19115)
+public class DefaultMetadataScope extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -91,10 +105,8 @@
      * given object are not recursively copied.
      *
      * @param object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(MetadataScope)
      */
-    public DefaultMetadataScope(final MetadataScope object) {
+    public DefaultMetadataScope(final DefaultMetadataScope object) {
         super(object);
         if (object != null) {
             resourceScope = object.getResourceScope();
@@ -103,37 +115,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultMetadataScope}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultMetadataScope} instance is created using the
-     *       {@linkplain #DefaultMetadataScope(MetadataScope) copy constructor} and returned.
-     *       Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultMetadataScope castOrCopy(final MetadataScope object) {
-        if (object == null || object instanceof DefaultMetadataScope) {
-            return (DefaultMetadataScope) object;
-        }
-        return new DefaultMetadataScope(object);
-    }
-
-    /**
      * Returns the code for the scope.
      *
      * @return the code for the scope.
      */
-    @Override
     @XmlElement(name = "resourceScope", required = true)
+    @UML(identifier="resourceScope", obligation=MANDATORY, specification=ISO_19115)
     public ScopeCode getResourceScope() {
         return resourceScope;
     }
@@ -153,8 +140,8 @@
      *
      * @return description of the scope, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "name")
+    @UML(identifier="name", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getName() {
         return name;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
index 4e498fa..38f99ed 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
@@ -21,7 +21,6 @@
 import java.util.Iterator;
 import java.util.Collection;
 import java.util.ConcurrentModificationException;
-import org.opengis.metadata.MetadataScope;
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
 
 
@@ -35,25 +34,25 @@
  * @since   0.5
  * @module
  */
-abstract class MetadataScopeAdapter<L> extends LegacyPropertyAdapter<L,MetadataScope> {
+abstract class MetadataScopeAdapter<L> extends LegacyPropertyAdapter<L,DefaultMetadataScope> {
     /**
      * @param scopes Value of {@link DefaultMetadata#getMetadataScopes()}.
      */
-    MetadataScopeAdapter(final Collection<MetadataScope> scopes) {
+    MetadataScopeAdapter(final Collection<DefaultMetadataScope> scopes) {
         super(scopes);
     }
 
     /**
      * Invoked (indirectly) by JAXB when adding a new scope code or scope name. This implementation searches
-     * for an existing {@link MetadataScope} instance with a free slot for the new value before to create a
+     * for an existing {@code MetadataScope} instance with a free slot for the new value before to create a
      * new {@link DefaultMetadataScope} instance.
      */
     @Override
     public boolean add(final L newValue) {
         int n = 0;
-        final Iterator<MetadataScope> it = elements.iterator();
+        final Iterator<DefaultMetadataScope> it = elements.iterator();
         while (it.hasNext()) {
-            MetadataScope scope = it.next();
+            DefaultMetadataScope scope = it.next();
             if (unwrap(scope) != null) {
                 n++;
                 continue;
@@ -64,18 +63,16 @@
              * But if the metadata is not modifiable, then we will need to clone it and replaces the element in
              * the collection.
              */
-            if (!(scope instanceof DefaultMetadataScope) ||
-                    ((DefaultMetadataScope) scope).state() == DefaultMetadataScope.State.FINAL)
-            {
+            if (scope.state() == DefaultMetadataScope.State.FINAL) {
                 scope = new DefaultMetadataScope(scope);
                 if (elements instanceof List<?>) {
-                    ((List<MetadataScope>) elements).set(n, scope);
+                    ((List<DefaultMetadataScope>) elements).set(n, scope);
                 } else {
                     /*
                      * Not a list. Delete all the remaining parts, substitute the value
                      * and reinsert everything in the same order.
                      */
-                    final MetadataScope[] remaining = new MetadataScope[elements.size() - n];
+                    final DefaultMetadataScope[] remaining = new DefaultMetadataScope[elements.size() - n];
                     remaining[0] = scope;
                     n = 1;
                     it.remove();
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
index c0e4fd4..5d80f31 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
@@ -69,7 +69,7 @@
     "types",
     "functions",
     "extents",
-    "objectiveOccurrences",
+    "objectiveOccurences",
     "pass",
     "sensingInstruments"
 })
@@ -78,7 +78,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 3312985886806161441L;
+    private static final long serialVersionUID = 8273806197892815938L;
 
     /**
      * Priority applied to the target.
@@ -104,7 +104,7 @@
     /**
      * Event or events associated with objective completion.
      */
-    private Collection<Event> objectiveOccurrences;
+    private Collection<Event> objectiveOccurences;
 
     /**
      * Pass of the platform over the objective.
@@ -139,7 +139,7 @@
             types                = copyCollection(object.getTypes(), ObjectiveType.class);
             functions            = copyCollection(object.getFunctions(), InternationalString.class);
             extents              = copyCollection(object.getExtents(), Extent.class);
-            objectiveOccurrences = copyCollection(object.getObjectiveOccurences(), Event.class);
+            objectiveOccurences  = copyCollection(object.getObjectiveOccurences(), Event.class);
             pass                 = copyCollection(object.getPass(), PlatformPass.class);
             sensingInstruments   = copyCollection(object.getSensingInstruments(), Instrument.class);
         }
@@ -289,45 +289,27 @@
     /**
      * Returns the event or events associated with objective completion.
      *
-     * @return events associated with objective completion.
-     *
-     * @since 1.0
-     */
-    @XmlElement(name = "objectiveOccurence", required = true)
-    public Collection<Event> getObjectiveOccurrences() {
-        return objectiveOccurrences = nonNullCollection(objectiveOccurrences, Event.class);
-    }
-
-    /**
-     * @deprecated Renamed {@link #getObjectiveOccurrences()}.
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method is misspelled (missing "r"). Its name may be fixed in GeoAPI 4.0.</div>
      *
      * @return events associated with objective completion.
      */
     @Override
-    @Deprecated
+    @XmlElement(name = "objectiveOccurence", required = true)
     public Collection<Event> getObjectiveOccurences() {
-        return getObjectiveOccurrences();
+        return objectiveOccurences = nonNullCollection(objectiveOccurences, Event.class);
     }
 
     /**
      * Sets the event or events associated with objective completion.
      *
-     * @param  newValues  the new objective occurrences values.
-     *
-     * @since 1.0
-     */
-    public void setObjectiveOccurrences(final Collection<? extends Event> newValues) {
-        objectiveOccurrences = writeCollection(newValues, objectiveOccurrences, Event.class);
-    }
-
-    /**
-     * @deprecated Renamed {@link #setObjectiveOccurrences(Collection)}.
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method is misspelled (missing "r"). Its name may be fixed in GeoAPI 4.0.</div>
      *
      * @param  newValues  the new objective occurrences values.
      */
-    @Deprecated
     public void setObjectiveOccurences(final Collection<? extends Event> newValues) {
-        setObjectiveOccurrences(newValues);
+        objectiveOccurences = writeCollection(newValues, objectiveOccurences, Event.class);
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/AbstractParty.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/AbstractParty.java
index f779b92..a9c5e27 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/AbstractParty.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/AbstractParty.java
@@ -23,13 +23,16 @@
 import javax.xml.bind.annotation.XmlSeeAlso;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Contact;
-import org.opengis.metadata.citation.Individual;
-import org.opengis.metadata.citation.Organisation;
-import org.opengis.metadata.citation.Party;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the individual and / or organization of the party.
@@ -39,6 +42,14 @@
  * <div class="preformat">{@code CI_Party}
  * {@code   └─name……} Name of the party.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Party} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -64,7 +75,8 @@
     DefaultIndividual.class,
     DefaultOrganisation.class
 })
-public class AbstractParty extends ISOMetadata implements Party {
+@UML(identifier="CI_Party", specification=ISO_19115)
+public class AbstractParty extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -103,10 +115,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Party)
      */
-    public AbstractParty(final Party object) {
+    public AbstractParty(final AbstractParty object) {
         super(object);
         if (object != null) {
             name        = object.getName();
@@ -115,47 +125,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is an instance of {@link Individual} or {@link Organisation},
-     *       then this method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.
-     *       Note that if the given object implements more than one of the above-cited interfaces,
-     *       then the {@code castOrCopy(…)} method to be used is unspecified.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code AbstractParty}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code AbstractParty} instance is created using the
-     *       {@linkplain #AbstractParty(Party) copy constructor} and returned.
-     *       Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static AbstractParty castOrCopy(final Party object) {
-        if (object instanceof Individual) {
-            return DefaultIndividual.castOrCopy((Individual) object);
-        }
-        if (object instanceof Organisation) {
-            return DefaultOrganisation.castOrCopy((Organisation) object);
-        }
-        if (object == null || object instanceof AbstractParty) {
-            return (AbstractParty) object;
-        }
-        return new AbstractParty(object);
-    }
-
-    /**
      * Return the name of the party.
      *
      * @return name of the party.
      */
-    @Override
     @XmlElement(name = "name")
+    @UML(identifier="name", obligation=CONDITIONAL, specification=ISO_19115)
     public InternationalString getName() {
         return name;
     }
@@ -175,8 +150,8 @@
      *
      * @return contact information for the party.
      */
-    @Override
     @XmlElement(name = "contactInfo")
+    @UML(identifier="contactInfo", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Contact> getContactInfo() {
         return contactInfo = nonNullCollection(contactInfo, Contact.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
index 1a8f066..9d219a0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
@@ -25,6 +25,7 @@
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.Characters;
 import org.apache.sis.util.CharSequences;
@@ -734,10 +735,10 @@
                      * Found a possible match. We will take the code space in account only if it is defined
                      * by both identifiers. If a code space is undefined, we consider that we have a match.
                      */
-                    if (identifier != null) {
-                        final String codeSpace = identifier.getCodeSpace();
-                        if (codeSpace != null) {
-                            final String cs = citId.getCodeSpace();
+                    if (identifier instanceof ReferenceIdentifier) {
+                        final String codeSpace = ((ReferenceIdentifier) identifier).getCodeSpace();
+                        if (codeSpace != null && citId instanceof ReferenceIdentifier) {
+                            final String cs = ((ReferenceIdentifier) citId).getCodeSpace();
                             if (cs != null && !equalsFiltered(codeSpace, cs)) {
                                 continue;       // Check other identifiers.
                             }
@@ -759,7 +760,8 @@
                     final CharSequence localPart = code.subSequence(++s, length);
                     for (it = citIds.iterator(); it.hasNext();) {
                         final Identifier id = it.next();
-                        if (equalsFiltered(codeSpace, id.getCodeSpace()) && equalsFiltered(localPart, id.getCode())) {
+                        final String cs = (id instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) id).getCodeSpace() : null;
+                        if (equalsFiltered(codeSpace, cs) && equalsFiltered(localPart, id.getCode())) {
                             return true;
                         }
                     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultAddress.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultAddress.java
index bc2211b..dda2592 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultAddress.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultAddress.java
@@ -57,7 +57,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -1709738216789373888L;
+    private static final long serialVersionUID = 1357443146723845129L;
 
     /**
      * State, province of the location.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
index f0795df..166ad72 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
@@ -39,6 +39,10 @@
 import org.apache.sis.xml.IdentifierSpace;
 import org.apache.sis.xml.IdentifierMap;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toDate;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toMilliseconds;
@@ -195,7 +199,6 @@
      *
      * @see #castOrCopy(Citation)
      */
-    @SuppressWarnings("deprecation")
     public DefaultCitation(final Citation object) {
         super(object);
         if (object != null) {
@@ -210,8 +213,11 @@
             series                  = object.getSeries();
             otherCitationDetails    = object.getOtherCitationDetails();
             collectiveTitle         = object.getCollectiveTitle();
-            onlineResources         = copyCollection(object.getOnlineResources(), OnlineResource.class);
-            graphics                = copyCollection(object.getGraphics(), BrowseGraphic.class);
+            if (object instanceof DefaultCitation) {
+                final DefaultCitation c = (DefaultCitation) object;
+                onlineResources = copyCollection(c.getOnlineResources(), OnlineResource.class);
+                graphics        = copyCollection(c.getGraphics(), BrowseGraphic.class);
+            }
             final String id1        = object.getISBN();
             final String id2        = object.getISSN();
             if (id1 != null || id2 != null) {
@@ -621,8 +627,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="onlineResource", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getOnlineResources() {
         return onlineResources = nonNullCollection(onlineResources, OnlineResource.class);
     }
@@ -645,8 +651,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="graphic", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<BrowseGraphic> getGraphics() {
         return graphics = nonNullCollection(graphics, BrowseGraphic.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java
index eafc326..f36f8f3 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java
@@ -28,17 +28,23 @@
 import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.citation.Telephone;
 import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.citation.TelephoneType;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.resources.Messages;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.gco.InternationalStringAdapter;
 import org.apache.sis.internal.metadata.Dependencies;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.util.CollectionsExt;
 
+// Branch-specific imports
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information required to enable contact with the responsible person and/or organization.
@@ -77,7 +83,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 2972124992825717056L;
+    private static final long serialVersionUID = -969735574940462381L;
 
     /**
      * Telephone numbers at which the organization or individual may be contacted.
@@ -137,12 +143,19 @@
     public DefaultContact(final Contact object) {
         super(object);
         if (object != null) {
-            phones              = copyCollection(object.getPhones(), Telephone.class);
-            addresses           = copyCollection(object.getAddresses(), Address.class);
-            onlineResources     = copyCollection(object.getOnlineResources(), OnlineResource.class);
             hoursOfService      = object.getHoursOfService();
             contactInstructions = object.getContactInstructions();
-            contactType         = object.getContactType();
+            if (object instanceof DefaultContact) {
+                final DefaultContact c = (DefaultContact) object;
+                phones          = copyCollection(c.getPhones(), Telephone.class);
+                addresses       = copyCollection(c.getAddresses(), Address.class);
+                onlineResources = copyCollection(c.getOnlineResources(), OnlineResource.class);
+                contactType     = c.getContactType();
+            } else {
+                phones          = singleton(object.getPhone(), Telephone.class);
+                addresses       = singleton(object.getAddress(), Address.class);
+                onlineResources = singleton(object.getOnlineResource(), OnlineResource.class);
+            }
         }
     }
 
@@ -178,8 +191,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="phone", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Telephone> getPhones() {
         return phones = nonNullCollection(phones, Telephone.class);
     }
@@ -218,8 +231,8 @@
 
     /**
      * Returns telephone numbers at which the organization or individual may be contacted.
-     * This method returns the first telephone number associated to {@link TelephoneType#VOICE}
-     * or {@link TelephoneType#FACSIMILE FACSIMILE}.
+     * This method returns the first telephone number associated to {@code TelephoneType.VOICE}
+     * or {@code TelephoneType.FACSIMILE FACSIMILE}.
      *
      * @return telephone numbers at which the organization or individual may be contacted, or {@code null}.
      *
@@ -233,16 +246,19 @@
         Telephone phone = null;
         if (FilterByVersion.LEGACY_METADATA.accept()) {
             final Collection<Telephone> phones = getPhones();
-            if (phones != null) {                                   // May be null on marshalling.
-                TelephoneType ignored = null;
+            if (phones != null) { // May be null on marshalling.
+                CodeList<?> ignored = null;
                 for (final Telephone c : phones) {
-                    final TelephoneType type = c.getNumberType();
-                    if (TelephoneType.VOICE.equals(type) || TelephoneType.FACSIMILE.equals(type)) {
-                        if (phone == null) {
-                            phone = c;
+                    if (c instanceof DefaultTelephone) {
+                        String name;
+                        final CodeList<?> type = ((DefaultTelephone) c).numberType;
+                        if (type != null && ("VOICE".equals(name = type.name()) || "FACSIMILE".equals(name))) {
+                            if (phone == null) {
+                                phone = c;
+                            }
+                        } else if (ignored == null) {
+                            ignored = type;
                         }
-                    } else if (ignored == null) {
-                        ignored = type;
                     }
                 }
                 if (ignored != null) {
@@ -251,7 +267,7 @@
                      * because we want the property to appear as "TelephoneType[FOO]" instead of "FOO".
                      */
                     Context.warningOccured(Context.current(), DefaultContact.class, "getPhone",
-                            Messages.class, Messages.Keys.IgnoredPropertyAssociatedTo_1, ignored.toString());
+                            Messages.class, Messages.Keys.IgnoredPropertyAssociatedTo_1, ignored);
                 }
             }
         }
@@ -275,10 +291,10 @@
             } else {
                 newValues = new ArrayList<>(4);
                 for (String number : newValue.getVoices()) {
-                    newValues.add(new DefaultTelephone(number, TelephoneType.VOICE));
+                    newValues.add(new DefaultTelephone(number, UnsupportedCodeList.VOICE));
                 }
                 for (String number : newValue.getFacsimiles()) {
-                    newValues.add(new DefaultTelephone(number, TelephoneType.FACSIMILE));
+                    newValues.add(new DefaultTelephone(number, UnsupportedCodeList.FACSIMILE));
                 }
             }
         }
@@ -292,8 +308,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="address", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Address> getAddresses() {
         return addresses = nonNullCollection(addresses, Address.class);
     }
@@ -348,8 +364,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="onlineResource", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getOnlineResources() {
         return onlineResources = nonNullCollection(onlineResources, OnlineResource.class);
     }
@@ -457,9 +473,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "contactType")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="contactType", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getContactType() {
         return contactType;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultIndividual.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultIndividual.java
index a38c9c3..d3e5ef3 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultIndividual.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultIndividual.java
@@ -20,14 +20,26 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.metadata.citation.Contact;
-import org.opengis.metadata.citation.Individual;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the party if the party is an individual.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Individual} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -47,7 +59,8 @@
     "positionName"
 })
 @XmlRootElement(name = "CI_Individual")
-public class DefaultIndividual extends AbstractParty implements Individual {
+@UML(identifier="CI_Individual", specification=ISO_19115)
+public class DefaultIndividual extends AbstractParty {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -85,10 +98,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Individual)
      */
-    public DefaultIndividual(final Individual object) {
+    public DefaultIndividual(final DefaultIndividual object) {
         super(object);
         if (object != null) {
             positionName = object.getPositionName();
@@ -96,37 +107,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultIndividual}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultIndividual} instance is created using the
-     *       {@linkplain #DefaultIndividual(Individual) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultIndividual castOrCopy(final Individual object) {
-        if (object == null || object instanceof DefaultIndividual) {
-            return (DefaultIndividual) object;
-        }
-        return new DefaultIndividual(object);
-    }
-
-    /**
      * Returns position of the individual in an organization, or {@code null} if none.
      *
      * @return position of the individual in an organization, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "positionName")
+    @UML(identifier="positionName", obligation=CONDITIONAL, specification=ISO_19115)
     public InternationalString getPositionName() {
         return positionName;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
index 1a01631..bc5f534 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
@@ -28,6 +28,11 @@
 import org.apache.sis.internal.jaxb.gco.URIAdapter;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about on-line sources from which the dataset, specification, or
@@ -68,7 +73,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 2627991335953178610L;
+    private static final long serialVersionUID = 1413613911128890864L;
 
     /**
      * Location (address) for on-line access using a Uniform Resource Locator address or
@@ -141,7 +146,9 @@
             name               = object.getName();
             description        = object.getDescription();
             function           = object.getFunction();
-            protocolRequest    = object.getProtocolRequest();
+            if (object instanceof DefaultOnlineResource) {
+                protocolRequest = ((DefaultOnlineResource) object).getProtocolRequest();
+            }
         }
     }
 
@@ -330,9 +337,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "protocolRequest")
     @XmlJavaTypeAdapter(StringAdapter.Since2014.class)
+    @UML(identifier="protocolRequest", obligation=OPTIONAL, specification=ISO_19115)
     public String getProtocolRequest() {
         return protocolRequest;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
index c8f65694..479685a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
@@ -21,14 +21,26 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.metadata.citation.Contact;
-import org.opengis.metadata.citation.Individual;
-import org.opengis.metadata.citation.Organisation;
 import org.opengis.metadata.identification.BrowseGraphic;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the party if the party is an organization.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Organisation} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -49,7 +61,8 @@
     "individual"
 })
 @XmlRootElement(name = "CI_Organisation")
-public class DefaultOrganisation extends AbstractParty implements Organisation {
+@UML(identifier="CI_Organisation", specification=ISO_19115)
+public class DefaultOrganisation extends AbstractParty {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -63,7 +76,7 @@
     /**
      * Individuals in the named organization.
      */
-    private Collection<Individual> individual;
+    private Collection<DefaultIndividual> individual;
 
     /**
      * Constructs an initially empty organization.
@@ -81,12 +94,12 @@
      */
     public DefaultOrganisation(final CharSequence name,
                                final BrowseGraphic logo,
-                               final Individual individual,
+                               final DefaultIndividual individual,
                                final Contact contactInfo)
     {
         super(name, contactInfo);
         this.logo       = singleton(logo, BrowseGraphic.class);
-        this.individual = singleton(individual, Individual.class);
+        this.individual = singleton(individual, DefaultIndividual.class);
     }
 
     /**
@@ -95,49 +108,22 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Organisation)
      */
-    public DefaultOrganisation(final Organisation object) {
+    public DefaultOrganisation(final DefaultOrganisation object) {
         super(object);
         if (object != null) {
             logo       = copyCollection(object.getLogo(), BrowseGraphic.class);
-            individual = copyCollection(object.getIndividual(), Individual.class);
+            individual = copyCollection(object.getIndividual(), DefaultIndividual.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultOrganisation}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultOrganisation} instance is created using the
-     *       {@linkplain #DefaultOrganisation(Organisation) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultOrganisation castOrCopy(final Organisation object) {
-        if (object == null || object instanceof DefaultOrganisation) {
-            return (DefaultOrganisation) object;
-        }
-        return new DefaultOrganisation(object);
-    }
-
-    /**
      * Returns the graphics identifying organization.
      *
      * @return graphics identifying organization, or an empty collection if there is none.
      */
-    @Override
     @XmlElement(name = "logo")
+    @UML(identifier="logo", obligation=CONDITIONAL, specification=ISO_19115)
     public Collection<BrowseGraphic> getLogo() {
         return logo = nonNullCollection(logo, BrowseGraphic.class);
     }
@@ -154,20 +140,30 @@
     /**
      * Returns the individuals in the named organization.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Individual} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return individuals in the named organization, or an empty collection.
      */
-    @Override
     @XmlElement(name = "individual")
-    public Collection<Individual> getIndividual() {
-        return individual = nonNullCollection(individual, Individual.class);
+    @UML(identifier="individual", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultIndividual> getIndividual() {
+        return individual = nonNullCollection(individual, DefaultIndividual.class);
     }
 
     /**
      * Sets the individuals in the named organization.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Individual} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new individuals in the named organization.
      */
-    public void setIndividual(final Collection<? extends Individual> newValues) {
-        individual = writeCollection(newValues, individual, Individual.class);
+    public void setIndividual(final Collection<? extends DefaultIndividual> newValues) {
+        individual = writeCollection(newValues, individual, DefaultIndividual.class);
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
index d680244..df1e9b5 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
@@ -24,13 +24,16 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.extent.Extent;
+import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.code.CI_RoleCode;
 
 // Branch-specific imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -43,6 +46,14 @@
  * {@code   │   └─name……………} Name of the party.
  * {@code   └─role………………………} Function performed by the responsible party.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Responsibility} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -69,7 +80,8 @@
 @XmlSeeAlso({
     DefaultResponsibleParty.class
 })
-public class DefaultResponsibility extends ISOMetadata implements Responsibility {
+@UML(identifier="CI_Responsibility", specification=ISO_19115)
+public class DefaultResponsibility extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -88,7 +100,7 @@
     /**
      * Information about the parties.
      */
-    private Collection<Party> parties;
+    private Collection<AbstractParty> parties;
 
     /**
      * Constructs an initially empty responsible party.
@@ -103,10 +115,10 @@
      * @param extent  spatial or temporal extent of the role, or {@code null}.
      * @param party   information about the party, or {@code null}.
      */
-    public DefaultResponsibility(final Role role, final Extent extent, final Party party) {
+    public DefaultResponsibility(final Role role, final Extent extent, final AbstractParty party) {
         this.role    = role;
         this.extents = singleton(extent, Extent.class);
-        this.parties = singleton(party, Party.class);
+        this.parties = singleton(party, AbstractParty.class);
     }
 
     /**
@@ -115,41 +127,29 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Responsibility)
      */
-    public DefaultResponsibility(final Responsibility object) {
+    public DefaultResponsibility(final DefaultResponsibility object) {
         super(object);
         if (object != null) {
             this.role    = object.getRole();
             this.extents = copyCollection(object.getExtents(), Extent.class);
-            this.parties = copyCollection(object.getParties(), Party.class);
+            this.parties = copyCollection(object.getParties(), AbstractParty.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultResponsibility}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultResponsibility} instance is created using the
-     *       {@linkplain #DefaultResponsibility(Responsibility) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       Responsibility contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
+     * Bridge constructor for {@link DefaultResponsibleParty#DefaultResponsibleParty(ResponsibleParty)}.
      */
-    public static DefaultResponsibility castOrCopy(final Responsibility object) {
-        if (object == null || object instanceof DefaultResponsibility) {
-            return (DefaultResponsibility) object;
+    DefaultResponsibility(final ResponsibleParty object) {
+        super(object);
+        if (object != null) {
+            this.role = object.getRole();
+            if (object instanceof DefaultResponsibility) {
+                final DefaultResponsibility c = (DefaultResponsibility) object;
+                this.extents = copyCollection(c.getExtents(), Extent.class);
+                this.parties = copyCollection(c.getParties(), AbstractParty.class);
+            }
         }
-        return new DefaultResponsibility(object);
     }
 
     /**
@@ -157,9 +157,9 @@
      *
      * @return function performed by the responsible party.
      */
-    @Override
     @XmlElement(name = "role", required = true)
     @XmlJavaTypeAdapter(CI_RoleCode.Since2014.class)
+    @UML(identifier="role", obligation=MANDATORY, specification=ISO_19115)
     public Role getRole() {
         return role;
     }
@@ -179,8 +179,8 @@
      *
      * @return the spatial or temporal extents of the role.
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="extent", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Extent> getExtents() {
         return extents = nonNullCollection(extents, Extent.class);
     }
@@ -197,21 +197,31 @@
     /**
      * Returns information about the parties.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Party} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the parties.
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<Party> getParties() {
-        return parties = nonNullCollection(parties, Party.class);
+    @UML(identifier="party", obligation=MANDATORY, specification=ISO_19115)
+    public Collection<AbstractParty> getParties() {
+        return parties = nonNullCollection(parties, AbstractParty.class);
     }
 
     /**
      * Sets information about the parties.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Party} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  new information about the parties.
      */
-    public void setParties(final Collection<? extends Party> newValues) {
-        parties = writeCollection(newValues, parties, Party.class);
+    public void setParties(final Collection<? extends AbstractParty> newValues) {
+        parties = writeCollection(newValues, parties, AbstractParty.class);
     }
 
 
@@ -239,7 +249,7 @@
     }
 
     @XmlElement(name = "party", required = true)
-    private Collection<Party> getParty() {
+    private Collection<AbstractParty> getParty() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getParties() : null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
index aee818c..4053749 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
@@ -24,10 +24,6 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.metadata.citation.Contact;
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Individual;
-import org.opengis.metadata.citation.Organisation;
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.citation.Role;
 import org.opengis.util.InternationalString;
@@ -50,8 +46,11 @@
  * {@code   └─party…………………………} Information about the parties.
  * {@code       └─name…………………} Name of the party.</div>
  *
- * @deprecated As of ISO 19115:2014, the {@code ResponsibleParty} type has been replaced by {@code Responsibility}
- *             to allow more flexible associations of individuals, organizations, and roles.
+ * <div class="warning"><b>Upcoming API change — deprecation</b><br>
+ * As of ISO 19115:2014, the {@code ResponsibleParty} type has been replaced by {@code Responsibility}
+ * to allow more flexible associations of individuals, organisations, and roles.
+ * This {@code ResponsibleParty} interface may be deprecated in GeoAPI 4.0.
+ * </div>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Touraïvane (IRD)
@@ -60,7 +59,6 @@
  * @since   0.3
  * @module
  */
-@Deprecated
 @XmlType(name = "CI_ResponsibleParty_Type", namespace = LegacyNamespaces.GMD, propOrder = {
     "individualName",
     "organisationName",
@@ -96,14 +94,29 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Responsibility)
      */
-    public DefaultResponsibleParty(final Responsibility object) {
+    public DefaultResponsibleParty(final DefaultResponsibility object) {
         super(object);
     }
 
     /**
+     * Constructs a new instance initialized with the values from the specified metadata object.
+     * This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
+     * given object are not recursively copied.
+     *
+     * @param object The metadata to copy values from, or {@code null} if none.
+     *
+     * @see #castOrCopy(ResponsibleParty)
+     */
+    public DefaultResponsibleParty(final ResponsibleParty object) {
+        super(object);
+        if (object != null && !(object instanceof DefaultResponsibility)) {
+            setIndividualName(object.getIndividualName());
+            setOrganisationName(object.getOrganisationName());
+        }
+    }
+
+    /**
      * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
      * This method performs the first applicable action in the following choices:
      *
@@ -112,7 +125,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code DefaultResponsibleParty}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code DefaultResponsibleParty} instance is created using the
-     *       {@linkplain #DefaultResponsibleParty(Responsibility) copy constructor}
+     *       {@linkplain #DefaultResponsibleParty(ResponsibleParty) copy constructor}
      *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -121,7 +134,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultResponsibleParty castOrCopy(final Responsibility object) {
+    public static DefaultResponsibleParty castOrCopy(final ResponsibleParty object) {
         if (object == null || object instanceof DefaultResponsibleParty) {
             return (DefaultResponsibleParty) object;
         }
@@ -140,12 +153,12 @@
      * @see #getPositionName()
      */
     private InternationalString getIndividual(final boolean position) {
-        final Collection<Party> parties = getParties();
-        InternationalString name = getName(parties, Individual.class, position);
+        final Collection<AbstractParty> parties = getParties();
+        InternationalString name = getName(parties, DefaultIndividual.class, position);
         if (name == null && parties != null) {
-            for (final Party party : parties) {
-                if (party instanceof Organisation) {
-                    name = getName(((Organisation) party).getIndividual(), Individual.class, position);
+            for (final AbstractParty party : parties) {
+                if (party instanceof DefaultOrganisation) {
+                    name = getName(((DefaultOrganisation) party).getIndividual(), DefaultIndividual.class, position);
                     if (name != null) {
                         break;
                     }
@@ -165,20 +178,20 @@
      * @see #getIndividualName()
      * @see #getPositionName()
      */
-    private static InternationalString getName(final Collection<? extends Party> parties,
-            final Class<? extends Party> type, final boolean position)
+    private static InternationalString getName(final Collection<? extends AbstractParty> parties,
+            final Class<? extends AbstractParty> type, final boolean position)
     {
         InternationalString name = null;
         if (parties != null) {                              // May be null on marshalling.
-            for (final Party party : parties) {
+            for (final AbstractParty party : parties) {
                 if (type.isInstance(party)) {
                     if (name != null) {
                         LegacyPropertyAdapter.warnIgnoredExtraneous(type, DefaultResponsibleParty.class,
-                                position ? "getPositionName" : (type == Individual.class)
+                                position ? "getPositionName" : (type == DefaultIndividual.class)
                                          ? "getIndividualName" : "getOrganisationName");
                         break;
                     }
-                    name = position ? ((Individual) party).getPositionName() : party.getName();
+                    name = position ? ((DefaultIndividual) party).getPositionName() : party.getName();
                 }
             }
         }
@@ -189,22 +202,22 @@
      * Sets the name of the first party of the given type.
      * If no existing party is found, generate a new party using the given creator.
      */
-    private void setName(final Class<? extends Party> type, final boolean position, final InternationalString name,
-                         final Function<InternationalString,Party> creator)
+    private void setName(final Class<? extends AbstractParty> type, final boolean position, final InternationalString name,
+                         final Function<InternationalString,AbstractParty> creator)
     {
-        final Collection<Party> parties = getParties();
+        final Collection<AbstractParty> parties = getParties();
         checkWritePermission(valueIfDefined(parties));
         if (parties != null) {                                  // May be null on unmarshalling.
-            final Iterator<Party> it = parties.iterator();
+            final Iterator<AbstractParty> it = parties.iterator();
             while (it.hasNext()) {
-                final Party party = it.next();
-                if (party instanceof AbstractParty && type.isInstance(party)) {
+                final AbstractParty party = it.next();
+                if (type.isInstance(party)) {
                     if (position) {
                         ((DefaultIndividual) party).setPositionName(name);
                     } else {
-                        ((AbstractParty) party).setName(name);
+                        party.setName(name);
                     }
-                    if (((AbstractParty) party).isEmpty()) {
+                    if (party.isEmpty()) {
                         it.remove();
                     }
                     return;
@@ -212,7 +225,7 @@
             }
         }
         if (name != null) {                             // If no party and name is null, there is nothing to set.
-            final Party party = creator.apply(name);
+            final AbstractParty party = creator.apply(name);
             if (parties != null) {                      // May be null on unmarshalling.
                 parties.add(party);
             } else {
@@ -226,9 +239,9 @@
      * Only one of {@code individualName}, {@link #getOrganisationName() organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation returns the name of the first {@link Individual} found in the collection of
+     * <p>This implementation returns the name of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}. If no individual is found in the parties, then this method fallbacks
-     * on the first {@linkplain Organisation#getIndividual() organisation member}.</p>
+     * on the first organisation member.</p>
      *
      * @return name, surname, given name and title of the responsible person, or {@code null}.
      *
@@ -248,7 +261,7 @@
      * Only one of {@code individualName}, {@link #getOrganisationName() organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation sets the name of the first {@link Individual} found in the collection of
+     * <p>This implementation sets the name of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}, or create a new individual if no existing instance was found.</p>
      *
      * @param  newValue  the new individual name, or {@code null} if none.
@@ -257,13 +270,13 @@
      */
     @Deprecated
     public void setIndividualName(final String newValue) {
-        setName(Individual.class, false, Types.toInternationalString(newValue), DefaultResponsibleParty::individual);
+        setName(DefaultIndividual.class, false, Types.toInternationalString(newValue), DefaultResponsibleParty::individual);
     }
 
     /**
      * Generates a new individual from the given name.
      */
-    private static Party individual(final InternationalString name) {
+    private static AbstractParty individual(final InternationalString name) {
         return new DefaultIndividual(name, null, null);
     }
 
@@ -272,7 +285,7 @@
      * {@link #getIndividualName() individualName}, {@code organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation returns the name of the first {@link Organisation}
+     * <p>This implementation returns the name of the first {@code Organisation}
      * found in the collection of {@linkplain #getParties() parties}.</p>
      *
      * @return name of the responsible organization, or {@code null}.
@@ -284,7 +297,7 @@
     @Dependencies("getParties")
     @XmlElement(name = "organisationName")
     public InternationalString getOrganisationName() {
-        return getName(getParties(), Organisation.class, false);
+        return getName(getParties(), DefaultOrganisation.class, false);
     }
 
     /**
@@ -292,7 +305,7 @@
      * {@link #getIndividualName() individualName}, {@code organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation sets the name of the first {@link Organisation} found in the collection of
+     * <p>This implementation sets the name of the first {@code Organisation} found in the collection of
      * {@linkplain #getParties() parties}, or create a new organization if no existing instance was found.</p>
      *
      * @param  newValue  the new organization name, or {@code null} if none.
@@ -301,13 +314,13 @@
      */
     @Deprecated
     public void setOrganisationName(final InternationalString newValue) {
-        setName(Organisation.class, false, newValue, DefaultResponsibleParty::organisation);
+        setName(DefaultOrganisation.class, false, newValue, DefaultResponsibleParty::organisation);
     }
 
     /**
      * Generates a new organization from the given name.
      */
-    private static Party organisation(final InternationalString name) {
+    private static AbstractParty organisation(final InternationalString name) {
         return new DefaultOrganisation(name, null, null, null);
     }
 
@@ -316,9 +329,9 @@
      * {@link #getIndividualName() individualName}, {@link #getOrganisationName() organisationName}
      * and {@code positionName} shall be provided.
      *
-     * <p>This implementation returns the position of the first {@link Individual} found in the collection of
+     * <p>This implementation returns the position of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}. If no individual is found in the parties, then this method fallbacks
-     * on the first {@linkplain Organisation#getIndividual() organisation member}.</p>
+     * on the first organisation member.</p>
      *
      * @return role or position of the responsible person, or {@code null}
      *
@@ -337,7 +350,7 @@
      * {@link #getIndividualName() individualName}, {@link #getOrganisationName() organisationName}
      * and {@code positionName} shall be provided.
      *
-     * <p>This implementation sets the position name of the first {@link Individual} found in the collection of
+     * <p>This implementation sets the position name of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}, or create a new individual if no existing instance was found.</p>
      *
      * @param  newValue  the new position name, or {@code null} if none.
@@ -352,7 +365,7 @@
     /**
      * Generates a new position from the given name.
      */
-    private static Party position(final InternationalString name) {
+    private static AbstractParty position(final InternationalString name) {
         return new DefaultIndividual(null, name, null);
     }
 
@@ -371,9 +384,9 @@
     @Dependencies("getParties")
     @XmlElement(name = "contactInfo")
     public Contact getContactInfo() {
-        final Collection<Party> parties = getParties();
+        final Collection<AbstractParty> parties = getParties();
         if (parties != null) {                                          // May be null on marshalling.
-            for (final Party party : parties) {
+            for (final AbstractParty party : parties) {
                 final Collection<? extends Contact> contacts = party.getContactInfo();
                 if (contacts != null) {                                 // May be null on marshalling.
                     for (final Contact contact : contacts) {
@@ -399,19 +412,17 @@
      */
     @Deprecated
     public void setContactInfo(final Contact newValue) {
-        final Collection<Party> parties = getParties();
+        final Collection<AbstractParty> parties = getParties();
         checkWritePermission(valueIfDefined(parties));
         if (parties != null) {                                  // May be null on unmarshalling.
-            final Iterator<Party> it = parties.iterator();
+            final Iterator<AbstractParty> it = parties.iterator();
             while (it.hasNext()) {
-                final Party party = it.next();
-                if (party instanceof AbstractParty) {
-                    ((AbstractParty) party).setContactInfo(newValue != null ? Collections.singleton(newValue) : null);
-                    if (((AbstractParty) party).isEmpty()) {
-                        it.remove();
-                    }
-                    return;
+                final AbstractParty party = it.next();
+                party.setContactInfo(newValue != null ? Collections.singleton(newValue) : null);
+                if (party.isEmpty()) {
+                    it.remove();
                 }
+                return;
             }
         }
         /*
@@ -419,7 +430,7 @@
          * it should be an individual or an organization. Arbitrarily choose an individual for now.
          */
         if (newValue != null) {
-            final Party party = new DefaultIndividual(null, null, newValue);
+            final AbstractParty party = new DefaultIndividual(null, null, newValue);
             if (parties != null) {
                 parties.add(party);
             } else {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
index c1b369d..e6443de 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
@@ -24,7 +24,6 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.citation.Telephone;
-import org.opengis.metadata.citation.TelephoneType;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.jaxb.FilterByVersion;
@@ -33,6 +32,16 @@
 import org.apache.sis.internal.jaxb.code.CI_TelephoneTypeCode;
 import org.apache.sis.internal.metadata.Dependencies;
 
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
+// Branch-specific imports
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import org.apache.sis.internal.geoapi.evolution.InterimType;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
+
 
 /**
  * Telephone numbers for contacting the responsible individual or organization.
@@ -95,7 +104,7 @@
     /**
      * Type of telephone number.
      */
-    private TelephoneType numberType;
+    CodeList<?> numberType;
 
     /**
      * Constructs a default telephone.
@@ -111,7 +120,7 @@
      *
      * @since 0.5
      */
-    public DefaultTelephone(final String number, final TelephoneType numberType) {
+    DefaultTelephone(final String number, final CodeList<?> numberType) {
         this.number     = number;
         this.numberType = numberType;
     }
@@ -128,8 +137,13 @@
     public DefaultTelephone(final Telephone object) {
         super(object);
         if (object != null) {
-            number     = object.getNumber();
-            numberType = object.getNumberType();
+            if (object instanceof DefaultTelephone) {
+                number     = ((DefaultTelephone) object).getNumber();
+                numberType = ((DefaultTelephone) object).numberType;
+            } else {
+                setVoices(object.getVoices());
+                setFacsimiles(object.getFacsimiles());
+            }
         }
     }
 
@@ -165,9 +179,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "number", required = true)
     @XmlJavaTypeAdapter(StringAdapter.Since2014.class)
+    @UML(identifier="number", obligation=MANDATORY, specification=ISO_19115)
     public String getNumber() {
         return number;
     }
@@ -186,26 +200,63 @@
 
     /**
      * Returns the type of telephone number, or {@code null} if none.
+     * If non-null, the type can be {@code "VOICE"}, {@code "FACSIMILE"} or {@code "SMS"}.
+     *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The return type will be changed to the {@code TelephoneType} code list
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
      *
      * @return type of telephone number, or {@code null} if none.
      *
      * @since 0.5
      */
-    @Override
+    @InterimType(UnsupportedCodeList.class)
     @XmlElement(name = "numberType")
     @XmlJavaTypeAdapter(CI_TelephoneTypeCode.Since2014.class)
-    public TelephoneType getNumberType() {
+    @UML(identifier="numberType", obligation=OPTIONAL, specification=ISO_19115)
+    public CodeList<?> getNumberType() {
         return numberType;
     }
 
     /**
      * Sets the type of telephone number.
+     * If non-null, the type can only be {@code "VOICE"}, {@code "FACSIMILE"} or {@code "SMS"}.
+     *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The argument type will be changed to the {@code TelephoneType} code list when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@preformat java
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     * }
+     * </div>
      *
      * @param  newValue  the new type of telephone number.
      *
      * @since 0.5
      */
-    public void setNumberType(final TelephoneType newValue) {
+    public void setNumberType(final CodeList<?> newValue) {
         checkWritePermission(numberType);
         numberType = newValue;
     }
@@ -273,7 +324,7 @@
      * @return telephone numbers by which individuals can speak to the responsible organization or individual.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #getNumber() number}
-     *             with {@link TelephoneType#VOICE}.
+     *             with {@code TelephoneType.VOICE}.
      */
     @Override
     @Deprecated
@@ -281,9 +332,9 @@
     @XmlElement(name = "voice", namespace = LegacyNamespaces.GMD)
     public final Collection<String> getVoices() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            return new LegacyTelephones(getOwner(), TelephoneType.VOICE);
+            return new LegacyTelephones(getOwner(), UnsupportedCodeList.VOICE);
         }
-        return null;                    // Marshalling newer ISO 19115-3 document.
+        return null;
     }
 
     /**
@@ -294,7 +345,7 @@
      * @param  newValues  the new telephone numbers, or {@code null} if none.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #setNumber(String) number}
-     *             with {@link TelephoneType#VOICE}.
+     *             code {@code TelephoneType.VOICE}.
      */
     @Deprecated
     public void setVoices(final Collection<? extends String> newValues) {
@@ -309,7 +360,7 @@
      * @return telephone numbers of a facsimile machine for the responsible organization or individual.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #getNumber() number}
-     *             with {@link TelephoneType#FACSIMILE}.
+     *             code {@code TelephoneType.FACSIMILE}.
      */
     @Override
     @Deprecated
@@ -317,7 +368,7 @@
     @XmlElement(name = "facsimile", namespace = LegacyNamespaces.GMD)
     public final Collection<String> getFacsimiles() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            return new LegacyTelephones(getOwner(), TelephoneType.FACSIMILE);
+            return new LegacyTelephones(getOwner(), UnsupportedCodeList.FACSIMILE);
         }
         return null;                    // Marshalling newer ISO 19115-3 document.
     }
@@ -330,7 +381,7 @@
      * @param  newValues  the new telephone number, or {@code null} if none.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #setNumber(String) number}
-     *             with {@link TelephoneType#FACSIMILE}.
+     *             with {@code TelephoneType.FACSIMILE}.
      */
     @Deprecated
     public void setFacsimiles(final Collection<? extends String> newValues) {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/LegacyTelephones.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/LegacyTelephones.java
index 592a097..2a07bf7 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/LegacyTelephones.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/LegacyTelephones.java
@@ -18,8 +18,8 @@
 
 import java.util.Collection;
 import java.util.Iterator;
+import org.opengis.util.CodeList;
 import org.opengis.metadata.citation.Telephone;
-import org.opengis.metadata.citation.TelephoneType;
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
 
 
@@ -36,14 +36,14 @@
 final class LegacyTelephones extends LegacyPropertyAdapter<String,Telephone> {
     /**
      * The type of telephone number.
-     * Either {@link TelephoneType#VOICE} or {@link TelephoneType#FACSIMILE}.
+     * Either {@code UnsupportedCodeList.VOICE} or {@code UnsupportedCodeList.FACSIMILE}.
      */
-    private final TelephoneType type;
+    private final CodeList<?> type;
 
     /**
      * Wraps the given telephone list for the given type.
      */
-    LegacyTelephones(final Collection<Telephone> telephones, final TelephoneType type) {
+    LegacyTelephones(final Collection<Telephone> telephones, final CodeList<?> type) {
         super(telephones);
         this.type = type;
     }
@@ -61,8 +61,13 @@
      */
     @Override
     protected String unwrap(final Telephone container) {
-        if (container != null && type.equals(container.getNumberType())) {
-            return container.getNumber();
+        if (container instanceof DefaultTelephone) {
+            final CodeList<?> ct = ((DefaultTelephone) container).numberType;
+            if (ct != null) {
+                if (type.name().equals(ct.name())) {
+                    return ((DefaultTelephone) container).getNumber();
+                }
+            }
         }
         return null;
     }
@@ -73,10 +78,10 @@
     @Override
     protected boolean update(final Telephone container, final String value) {
         if (container instanceof DefaultTelephone) {
-            final TelephoneType ct = container.getNumberType();
-            if (ct == null || ct.equals(type)) {
+            final CodeList<?> ct = ((DefaultTelephone) container).numberType;
+            if (ct == null || type.name().equals(ct.name())) {
                 if (ct == null) {
-                    ((DefaultTelephone) container).setNumberType(type);
+                    ((DefaultTelephone) container).numberType = type;
                 }
                 ((DefaultTelephone) container).setNumber(value);
                 return true;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
index 39b7e9c..a90b825 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
@@ -24,19 +24,23 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.Responsibility;
-import org.opengis.metadata.constraint.Releasability;
 import org.opengis.metadata.identification.BrowseGraphic;
 import org.opengis.metadata.constraint.Constraints;
 import org.opengis.metadata.constraint.LegalConstraints;
 import org.opengis.metadata.constraint.SecurityConstraints;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.metadata.MD_Releasability;
 import org.apache.sis.internal.jaxb.metadata.MD_Scope;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Restrictions on the access and use of a resource or metadata.
@@ -102,12 +106,12 @@
     /**
      * Information concerning the parties to whom the resource can or cannot be released.
      */
-    private Releasability releasability;
+    private DefaultReleasability releasability;
 
     /**
      * Party responsible for the resource constraints.
      */
-    private Collection<Responsibility> responsibleParties;
+    private Collection<DefaultResponsibility> responsibleParties;
 
     /**
      * Constructs an initially empty constraints.
@@ -137,11 +141,14 @@
         super(object);
         if (object != null) {
             useLimitations             = copyCollection(object.getUseLimitations(), InternationalString.class);
-            constraintApplicationScope = object.getConstraintApplicationScope();
-            graphics                   = copyCollection(object.getGraphics(), BrowseGraphic.class);
-            references                 = copyCollection(object.getReferences(), Citation.class);
-            releasability              = object.getReleasability();
-            responsibleParties         = copyCollection(object.getResponsibleParties(), Responsibility.class);
+            if (object instanceof DefaultConstraints) {
+                final DefaultConstraints c = (DefaultConstraints) object;
+                constraintApplicationScope = c.getConstraintApplicationScope();
+                graphics                   = copyCollection(c.getGraphics(), BrowseGraphic.class);
+                references                 = copyCollection(c.getReferences(), Citation.class);
+                releasability              = c.getReleasability();
+                responsibleParties         = copyCollection(c.getResponsibleParties(), DefaultResponsibility.class);
+            }
         }
     }
 
@@ -212,9 +219,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "constraintApplicationScope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="constraintApplicationScope", obligation=OPTIONAL, specification=ISO_19115)
     public Scope getConstraintApplicationScope() {
         return constraintApplicationScope;
     }
@@ -238,8 +245,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="graphic", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<BrowseGraphic> getGraphics() {
         return graphics = nonNullCollection(graphics, BrowseGraphic.class);
     }
@@ -263,8 +270,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="reference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getReferences() {
         return references = nonNullCollection(references, Citation.class);
     }
@@ -283,25 +290,35 @@
     /**
      * Returns information concerning the parties to whom the resource can or cannot be released.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The return type will be changed to the {@code Releasability} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information concerning the parties to whom the resource can or cannot be released, or {@code null} if none.
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "releasability")
     @XmlJavaTypeAdapter(MD_Releasability.Since2014.class)
-    public Releasability getReleasability() {
+    @UML(identifier="releasability", obligation=OPTIONAL, specification=ISO_19115)
+    public DefaultReleasability getReleasability() {
         return releasability;
     }
 
     /**
      * Sets the information concerning the parties to whom the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The argument type will be changed to the {@code Releasability} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValue  the new information concerning the parties to whom the resource can or cannot be released.
      *
      * @since 0.5
      */
-    public void setReleasability(final Releasability newValue) {
+    public void setReleasability(final DefaultReleasability newValue) {
         checkWritePermission(releasability);
         releasability = newValue;
     }
@@ -309,25 +326,35 @@
     /**
      * Returns the parties responsible for the resource constraints.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return parties responsible for the resource constraints.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<Responsibility> getResponsibleParties() {
-        return responsibleParties = nonNullCollection(responsibleParties, Responsibility.class);
+    @UML(identifier="responsibleParty", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultResponsibility> getResponsibleParties() {
+        return responsibleParties = nonNullCollection(responsibleParties, DefaultResponsibility.class);
     }
 
     /**
      * Sets the parties responsible for the resource constraints.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new parties responsible for the resource constraints.
      *
      * @since 0.5
      */
-    public void setResponsibleParties(final Collection<? extends Responsibility> newValues) {
-        responsibleParties = writeCollection(newValues, responsibleParties, Responsibility.class);
+    public void setResponsibleParties(final Collection<? extends DefaultResponsibility> newValues) {
+        responsibleParties = writeCollection(newValues, responsibleParties, DefaultResponsibility.class);
     }
 
 
@@ -360,7 +387,7 @@
     }
 
     @XmlElement(name = "responsibleParty")
-    private Collection<Responsibility> getResponsibleParty() {
+    private Collection<DefaultResponsibility> getResponsibleParty() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getResponsibleParties() : null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
index e18747d..410cbb2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
@@ -21,15 +21,27 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.citation.Responsibility;
-import org.opengis.metadata.constraint.Releasability;
 import org.opengis.metadata.constraint.Restriction;
 import org.apache.sis.metadata.iso.ISOMetadata;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
  * Information about resource release constraints.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Releasability} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -50,7 +62,8 @@
     "disseminationConstraints"
 })
 @XmlRootElement(name = "MD_Releasability")
-public class DefaultReleasability extends ISOMetadata implements Releasability {
+@UML(identifier="MD_Releasability", specification=ISO_19115)
+public class DefaultReleasability extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -59,7 +72,7 @@
     /**
      * Party to which the release statement applies.
      */
-    private Collection<Responsibility> addressees;
+    private Collection<DefaultResponsibility> addressees;
 
     /**
      * Release statement.
@@ -83,61 +96,44 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Releasability)
      */
-    public DefaultReleasability(final Releasability object) {
+    public DefaultReleasability(final DefaultReleasability object) {
         super(object);
         if (object != null) {
-            addressees                = copyCollection(object.getAddressees(), Responsibility.class);
+            addressees                = copyCollection(object.getAddressees(), DefaultResponsibility.class);
             statement                 = object.getStatement();
             disseminationConstraints  = copyCollection(object.getDisseminationConstraints(), Restriction.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultReleasability}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultReleasability} instance is created using the
-     *       {@linkplain #DefaultReleasability(Releasability) copy constructor} and returned.
-     *       Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultReleasability castOrCopy(final Releasability object) {
-        if (object == null || object instanceof DefaultReleasability) {
-            return (DefaultReleasability) object;
-        }
-        return new DefaultReleasability(object);
-    }
-
-    /**
      * Returns the parties to which the release statement applies.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return parties to which the release statement applies.
      */
-    @Override
     @XmlElement(name = "addressee")
-    public Collection<Responsibility> getAddressees() {
-        return addressees = nonNullCollection(addressees, Responsibility.class);
+    @UML(identifier="addressee", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultResponsibility> getAddressees() {
+        return addressees = nonNullCollection(addressees, DefaultResponsibility.class);
     }
 
     /**
      * Sets the parties to which the release statement applies.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new parties.
      */
-    public void setAddressees(final Collection<? extends Responsibility> newValues) {
-        addressees = writeCollection(newValues, addressees, Responsibility.class);
+    public void setAddressees(final Collection<? extends DefaultResponsibility> newValues) {
+        addressees = writeCollection(newValues, addressees, DefaultResponsibility.class);
     }
 
     /**
@@ -148,7 +144,7 @@
      * @deprecated Renamed {@link #setAddressees(Collection)}.
      */
     @Deprecated
-    public void getAddressees(final Collection<? extends Responsibility> newValues) {
+    public void getAddressees(final Collection<? extends DefaultResponsibility> newValues) {
         setAddressees(newValues);
     }
 
@@ -157,8 +153,8 @@
      *
      * @return release statement, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "statement")
+    @UML(identifier="statement", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getStatement() {
         return statement;
     }
@@ -178,8 +174,8 @@
      *
      * @return components in determining releasability.
      */
-    @Override
     @XmlElement(name = "disseminationConstraints")
+    @UML(identifier="disseminationConstraints", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Restriction> getDisseminationConstraints() {
         return disseminationConstraints = nonNullCollection(disseminationConstraints, Restriction.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java
index 8e4def7..afcff86 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java
@@ -20,11 +20,16 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
-import org.opengis.metadata.content.AttributeGroup;
 import org.opengis.metadata.content.CoverageContentType;
 import org.opengis.metadata.content.RangeDimension;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about content type for groups of attributes for a specific
@@ -34,6 +39,14 @@
  * <div class="preformat">{@code MD_AttributeGroup}
  * {@code   └─contentType……} Content type</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code AttributeGroup} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -55,7 +68,8 @@
     "attributes"
 })
 @XmlRootElement(name = "MD_AttributeGroup")
-public class DefaultAttributeGroup extends ISOMetadata implements AttributeGroup {
+@UML(identifier="MD_AttributeGroup", specification=ISO_19115)
+public class DefaultAttributeGroup extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -94,10 +108,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(AttributeGroup)
      */
-    public DefaultAttributeGroup(final AttributeGroup object) {
+    public DefaultAttributeGroup(final DefaultAttributeGroup object) {
         super(object);
         if (object != null) {
             contentTypes = copyCollection(object.getContentTypes(), CoverageContentType.class);
@@ -106,37 +118,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultAttributeGroup}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultAttributeGroup} instance is created using the
-     *       {@linkplain #DefaultAttributeGroup(AttributeGroup) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultAttributeGroup castOrCopy(final AttributeGroup object) {
-        if (object == null || object instanceof DefaultAttributeGroup) {
-            return (DefaultAttributeGroup) object;
-        }
-        return new DefaultAttributeGroup(object);
-    }
-
-    /**
      * Returns the types of information represented by the value(s).
      *
      * @return the types of information represented by the value(s).
      */
-    @Override
     @XmlElement(name = "contentType", required = true)
+    @UML(identifier="contentType", obligation=MANDATORY, specification=ISO_19115)
     public Collection<CoverageContentType> getContentTypes() {
         return contentTypes = nonNullCollection(contentTypes, CoverageContentType.class);
     }
@@ -155,8 +142,8 @@
      *
      * @return information on an attribute of the resource.
      */
-    @Override
     @XmlElement(name = "attribute")
+    @UML(identifier="attribute", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<RangeDimension> getAttributes() {
         return attributes = nonNullCollection(attributes, RangeDimension.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java
index 9597665..9046ca2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java
@@ -33,6 +33,11 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Range of wavelengths in the electromagnetic spectrum.
@@ -145,9 +150,12 @@
     public DefaultBand(final Band object) {
         super(object);
         if (object != null) {
-            boundMin                 = object.getBoundMin();
-            boundMax                 = object.getBoundMax();
-            boundUnits               = object.getBoundUnits();
+            if (object instanceof DefaultBand) {
+                final DefaultBand c = (DefaultBand) object;
+                boundMin   = c.getBoundMin();
+                boundMax   = c.getBoundMax();
+                boundUnits = c.getBoundUnits();
+            }
             peakResponse             = object.getPeakResponse();
             toneGradation            = object.getToneGradation();
             bandBoundaryDefinition   = object.getBandBoundaryDefinition();
@@ -190,10 +198,10 @@
      *
      * @since 0.5
      */
-    @Override
     @ValueRange(minimum = 0)
     @XmlElement(name = "boundMin")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="boundMin", obligation=OPTIONAL, specification=ISO_19115)
     public Double getBoundMin() {
         return boundMin;
     }
@@ -222,10 +230,10 @@
      *
      * @since 0.5
      */
-    @Override
     @ValueRange(minimum = 0)
     @XmlElement(name = "boundMax")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="boundMax", obligation=OPTIONAL, specification=ISO_19115)
     public Double getBoundMax() {
         return boundMax;
     }
@@ -254,9 +262,9 @@
      *
      * @see org.apache.sis.measure.Units#NANOMETRE
      */
-    @Override
     @XmlElement(name = "boundUnits")
     @XmlJavaTypeAdapter(UnitAdapter.Since2014.class)
+    @UML(identifier="boundUnits", obligation=OPTIONAL, specification=ISO_19115)
     public Unit<Length> getBoundUnits() {
         return boundUnits;
     }
@@ -295,6 +303,39 @@
     }
 
     /**
+     * Returns the units of data as a unit of length.
+     *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, the units of wavelength is rather {@code boundUnits}.
+     * The restriction for units of length in this {@code units} property may be relaxed in GeoAPI 4.0.
+     * </div>
+     *
+     * @return The units of data.
+     */
+    @Override
+    public Unit<Length> getUnits() {
+        final Unit<?> units = super.getUnits();
+        return (units != null) ? units.asType(Length.class) : null;
+    }
+
+    /**
+     * Sets the units of data as a unit of length.
+     *
+     * <div class="warning"><b>Upcoming precondition change — relaxation</b><br>
+     * The current implementation requires the unit to be an instance of {@code Unit<Length>},
+     * otherwise a {@link ClassCastException} is thrown. This is because the value returned by
+     * {@link #getUnits()} was restricted by ISO 19115:2003 to units of length.
+     * However this restriction may be relaxed in GeoAPI 4.0.
+     * </div>
+     *
+     * @param newValue The new units of data as an instance of {@code Unit<Length>}.
+     */
+    @Override
+    public void setUnits(final Unit<?> newValue) {
+        super.setUnits(newValue != null ? newValue.asType(Length.class) : null);
+    }
+
+    /**
      * Returns the wavelength at which the response is the highest.
      * The units of measurement is given by {@link #getBoundUnits()}.
      *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
index 61abb0d..11d3d9b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
@@ -24,7 +24,6 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.Identifier;
-import org.opengis.metadata.content.AttributeGroup;
 import org.opengis.metadata.content.CoverageContentType;
 import org.opengis.metadata.content.CoverageDescription;
 import org.opengis.metadata.content.ImageDescription;
@@ -38,6 +37,10 @@
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.metadata.MD_Identifier;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
 
 
@@ -97,7 +100,7 @@
     /**
      * Information on attribute groups of the resource.
      */
-    private Collection<AttributeGroup> attributeGroups;
+    private Collection<DefaultAttributeGroup> attributeGroups;
 
     /**
      * Provides the description of the specific range elements of a coverage.
@@ -123,9 +126,11 @@
         super(object);
         if (object != null) {
             attributeDescription     = object.getAttributeDescription();
-            processingLevelCode      = object.getProcessingLevelCode();
-            attributeGroups          = copyCollection(object.getAttributeGroups(), AttributeGroup.class);
             rangeElementDescriptions = copyCollection(object.getRangeElementDescriptions(), RangeElementDescription.class);
+            if (object instanceof DefaultCoverageDescription) {
+                processingLevelCode  = ((DefaultCoverageDescription) object).getProcessingLevelCode();
+                attributeGroups      = copyCollection(((DefaultCoverageDescription) object).getAttributeGroups(), DefaultAttributeGroup.class);
+            }
         }
     }
 
@@ -190,9 +195,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "processingLevelCode")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="processingLevelCode", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getProcessingLevelCode() {
         return processingLevelCode;
     }
@@ -212,25 +217,35 @@
     /**
      * Returns information on attribute groups of the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AttributeGroup} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information on attribute groups of the resource.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<AttributeGroup> getAttributeGroups() {
-        return attributeGroups = nonNullCollection(attributeGroups, AttributeGroup.class);
+    @UML(identifier="attributeGroup", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultAttributeGroup> getAttributeGroups() {
+        return attributeGroups = nonNullCollection(attributeGroups, DefaultAttributeGroup.class);
     }
 
     /**
      * Sets information on attribute groups of the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AttributeGroup} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information on attribute groups of the resource.
      *
      * @since 0.5
      */
-    public void setAttributeGroups(final Collection<? extends AttributeGroup> newValues) {
-        attributeGroups = writeCollection(newValues, attributeGroups, AttributeGroup.class);
+    public void setAttributeGroups(final Collection<? extends DefaultAttributeGroup> newValues) {
+        attributeGroups = writeCollection(newValues, attributeGroups, DefaultAttributeGroup.class);
     }
 
     /**
@@ -248,9 +263,9 @@
     public CoverageContentType getContentType() {
         CoverageContentType type = null;
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            final Collection<AttributeGroup> groups = getAttributeGroups();
+            final Collection<DefaultAttributeGroup> groups = getAttributeGroups();
             if (groups != null) {                                               // May be null on marshalling.
-                for (final AttributeGroup g : groups) {
+                for (final DefaultAttributeGroup g : groups) {
                     final Collection<? extends CoverageContentType> contentTypes = g.getContentTypes();
                     if (contentTypes != null) {                                 // May be null on marshalling.
                         for (final CoverageContentType t : contentTypes) {
@@ -281,13 +296,11 @@
     public void setContentType(final CoverageContentType newValue) {
         checkWritePermission(valueIfDefined(attributeGroups));
         final Collection<CoverageContentType> newValues = CollectionsExt.singletonOrEmpty(newValue);
-        Collection<AttributeGroup> groups = attributeGroups;
+        Collection<DefaultAttributeGroup> groups = attributeGroups;
         if (groups != null) {
-            for (final AttributeGroup group : groups) {
-                if (group instanceof DefaultAttributeGroup) {
-                    ((DefaultAttributeGroup) group).setContentTypes(newValues);
-                    return;
-                }
+            for (final DefaultAttributeGroup group : groups) {
+                group.setContentTypes(newValues);
+                return; // Actually stop at the first instance.
             }
         }
         final DefaultAttributeGroup group = new DefaultAttributeGroup();
@@ -295,7 +308,7 @@
         if (groups != null) {
             groups.add(group);
         } else {
-            groups = Collections.singleton(group);
+            groups = Collections.<DefaultAttributeGroup>singleton(group);
         }
         setAttributeGroups(groups);
     }
@@ -314,24 +327,24 @@
     @XmlElement(name = "dimension", namespace = LegacyNamespaces.GMD)
     public final Collection<RangeDimension> getDimensions() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new LegacyPropertyAdapter<RangeDimension,AttributeGroup>(getAttributeGroups()) {
+        return new LegacyPropertyAdapter<RangeDimension,DefaultAttributeGroup>(getAttributeGroups()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected AttributeGroup wrap(final RangeDimension value) {
+            @Override protected DefaultAttributeGroup wrap(final RangeDimension value) {
                 final DefaultAttributeGroup container = new DefaultAttributeGroup();
                 container.setAttributes(CollectionsExt.singletonOrEmpty(value));
                 return container;
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected RangeDimension unwrap(final AttributeGroup container) {
+            @Override protected RangeDimension unwrap(final DefaultAttributeGroup container) {
                 return getSingleton(container.getAttributes(), RangeDimension.class,
                         this, DefaultCoverageDescription.class, "getDimensions");
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final AttributeGroup container, final RangeDimension value) {
+            @Override protected boolean update(final DefaultAttributeGroup container, final RangeDimension value) {
                 if (container instanceof DefaultAttributeGroup) {
-                    ((DefaultAttributeGroup) container).setAttributes(CollectionsExt.singletonOrEmpty(value));
+                    container.setAttributes(CollectionsExt.singletonOrEmpty(value));
                     return true;
                 }
                 return false;
@@ -393,7 +406,7 @@
      * If (and only if) marshalling an older standard version, we omit this attribute.
      */
     @XmlElement(name = "attributeGroup")
-    private Collection<AttributeGroup> getAttributeGroup() {
+    private Collection<DefaultAttributeGroup> getAttributeGroup() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getAttributeGroups() : null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
index 8535856..5c54394 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
@@ -26,7 +26,6 @@
 import org.opengis.util.GenericName;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.content.FeatureCatalogueDescription;
-import org.opengis.metadata.content.FeatureTypeInfo;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.jaxb.lan.PT_Locale;
@@ -34,6 +33,11 @@
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
 import org.apache.sis.internal.jaxb.lan.LocaleAndCharset;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
 
 
@@ -104,7 +108,7 @@
     /**
      * Subset of feature types from cited feature catalogue occurring in resource.
      */
-    private Collection<FeatureTypeInfo> featureTypes;
+    private Collection<DefaultFeatureTypeInfo> featureTypes;
 
     /**
      * Complete bibliographic reference to one or more external feature catalogues.
@@ -131,9 +135,14 @@
         if (object != null) {
             compliant                 = object.isCompliant();
             includedWithDataset       = object.isIncludedWithDataset();
-            locales                   = copyMap(object.getLocalesAndCharsets(), Locale.class);
-            featureTypes              = copyCollection(object.getFeatureTypeInfo(), FeatureTypeInfo.class);
             featureCatalogueCitations = copyCollection(object.getFeatureCatalogueCitations(), Citation.class);
+            if (object instanceof DefaultFeatureCatalogueDescription) {
+                locales = copyMap(((DefaultFeatureCatalogueDescription) object).getLocalesAndCharsets(), Locale.class);
+                featureTypes = copyCollection(((DefaultFeatureCatalogueDescription) object).getFeatureTypeInfo(), DefaultFeatureTypeInfo.class);
+            } else {
+                setLanguages(copyCollection(object.getLanguages(), Locale.class));
+                setFeatureTypes(object.getFeatureTypes());
+            }
         }
     }
 
@@ -190,7 +199,7 @@
      *
      * @since 1.0
      */
-    @Override
+    @UML(identifier="locale", obligation=CONDITIONAL, specification=ISO_19115)
     // @XmlElement at the end of this class.
     public Map<Locale,Charset> getLocalesAndCharsets() {
         return locales = nonNullMap(locales, Locale.class);
@@ -259,25 +268,35 @@
     /**
      * Returns the subset of feature types from cited feature catalogue occurring in resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code FeatureTypeInfo} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return subset of feature types occurring in resource.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<FeatureTypeInfo> getFeatureTypeInfo() {
-        return featureTypes = nonNullCollection(featureTypes, FeatureTypeInfo.class);
+    @UML(identifier="featureTypes", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultFeatureTypeInfo> getFeatureTypeInfo() {
+        return featureTypes = nonNullCollection(featureTypes, DefaultFeatureTypeInfo.class);
     }
 
     /**
      * Sets the subset of feature types from cited feature catalogue occurring in resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code FeatureTypeInfo} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new feature types.
      *
      * @since 0.5
      */
-    public void setFeatureTypeInfo(final Collection<? extends FeatureTypeInfo> newValues) {
-        featureTypes = writeCollection(newValues, featureTypes, FeatureTypeInfo.class);
+    public void setFeatureTypeInfo(final Collection<? extends DefaultFeatureTypeInfo> newValues) {
+        featureTypes = writeCollection(newValues, featureTypes, DefaultFeatureTypeInfo.class);
     }
 
     /**
@@ -293,21 +312,21 @@
     @XmlElement(name = "featureTypes", namespace = LegacyNamespaces.GMD)
     public final Collection<GenericName> getFeatureTypes() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new LegacyPropertyAdapter<GenericName,FeatureTypeInfo>(getFeatureTypeInfo()) {
+        return new LegacyPropertyAdapter<GenericName,DefaultFeatureTypeInfo>(getFeatureTypeInfo()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected FeatureTypeInfo wrap(final GenericName value) {
+            @Override protected DefaultFeatureTypeInfo wrap(final GenericName value) {
                 return new DefaultFeatureTypeInfo(value);
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected GenericName unwrap(final FeatureTypeInfo container) {
+            @Override protected GenericName unwrap(final DefaultFeatureTypeInfo container) {
                 return container.getFeatureTypeName();
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final FeatureTypeInfo container, final GenericName value) {
+            @Override protected boolean update(final DefaultFeatureTypeInfo container, final GenericName value) {
                 if (container instanceof DefaultFeatureTypeInfo) {
-                    ((DefaultFeatureTypeInfo) container).setFeatureTypeName(value);
+                    container.setFeatureTypeName(value);
                     return true;
                 }
                 return false;
@@ -368,7 +387,7 @@
      * If (and only if) marshalling an older standard version, we omit this attribute.
      */
     @XmlElement(name = "featureTypes")
-    private Collection<FeatureTypeInfo> getFeatureTypesInfo() {
+    private Collection<DefaultFeatureTypeInfo> getFeatureTypesInfo() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getFeatureTypeInfo() : null;
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
index b91babb..6ba9464 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
@@ -20,17 +20,30 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
 import org.opengis.util.GenericName;
-import org.opengis.metadata.content.FeatureTypeInfo;
 import org.apache.sis.measure.ValueRange;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the occurring feature type.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code FeatureTypeInfo} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -56,7 +69,8 @@
     "featureInstanceCount"
 })
 @XmlRootElement(name = "MD_FeatureTypeInfo")
-public class DefaultFeatureTypeInfo extends ISOMetadata implements FeatureTypeInfo {
+@UML(identifier="MD_FeatureTypeInfo", specification=ISO_19115)
+public class DefaultFeatureTypeInfo extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -100,10 +114,8 @@
      * </div>
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(FeatureTypeInfo)
      */
-    public DefaultFeatureTypeInfo(final FeatureTypeInfo object) {
+    public DefaultFeatureTypeInfo(final DefaultFeatureTypeInfo object) {
         super(object);
         if (object != null) {
             featureTypeName      = object.getFeatureTypeName();
@@ -112,39 +124,14 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultFeatureTypeInfo}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultFeatureTypeInfo} instance is created using the
-     *       {@linkplain #DefaultFeatureTypeInfo(FeatureTypeInfo) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultFeatureTypeInfo castOrCopy(final FeatureTypeInfo object) {
-        if (object == null || object instanceof DefaultFeatureTypeInfo) {
-            return (DefaultFeatureTypeInfo) object;
-        }
-        return new DefaultFeatureTypeInfo(object);
-    }
-
-    /**
      * Returns the name of the feature type.
      *
      * @return name of the feature type.
      *
      * @see org.apache.sis.feature.DefaultFeatureType#getName()
      */
-    @Override
     @XmlElement(name = "featureTypeName", required = true)
+    @UML(identifier="featureTypeName", obligation=MANDATORY, specification=ISO_19115)
     public GenericName getFeatureTypeName() {
         return featureTypeName;
     }
@@ -164,9 +151,9 @@
      *
      * @return the number of occurrence of feature instances for this feature types, or {@code null} if none.
      */
-    @Override
     @ValueRange(minimum = 1)
     @XmlElement(name = "featureInstanceCount")
+    @UML(identifier="featureInstanceCount", obligation=OPTIONAL, specification=ISO_19115)
     public Integer getFeatureInstanceCount() {
         return featureInstanceCount;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java
index 34bbcd3..6ec34d0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java
@@ -26,7 +26,7 @@
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.content.RangeDimension;
-import org.opengis.metadata.content.SampleDimension;
+import org.opengis.metadata.content.Band;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.internal.metadata.Dependencies;
@@ -34,6 +34,11 @@
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.gco.InternationalStringAdapter;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information on the range of each dimension of a cell measurement value.
@@ -106,8 +111,10 @@
         super(object);
         if (object != null) {
             sequenceIdentifier = object.getSequenceIdentifier();
-            description        = object.getDescription();
-            names              = copyCollection(object.getNames(), Identifier.class);
+            description        = object.getDescriptor();
+            if (object instanceof DefaultRangeDimension) {
+                names = copyCollection(((DefaultRangeDimension) object).getNames(), Identifier.class);
+            }
         }
     }
 
@@ -117,7 +124,7 @@
      *
      * <ul>
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is an instance of {@link SampleDimension}, then this method
+     *   <li>Otherwise if the given object is an instance of {@code SampleDimension}, then this method
      *       delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.</li>
      *   <li>Otherwise if the given object is already an instance of
      *       {@code DefaultRangeDimension}, then it is returned unchanged.</li>
@@ -132,8 +139,8 @@
      *         given object itself), or {@code null} if the argument was null.
      */
     public static DefaultRangeDimension castOrCopy(final RangeDimension object) {
-        if (object instanceof SampleDimension) {
-            return DefaultSampleDimension.castOrCopy((SampleDimension) object);
+        if (object instanceof Band) {
+            return DefaultBand.castOrCopy((Band) object);
         }
         // Intentionally tested after the sub-interfaces.
         if (object == null || object instanceof DefaultRangeDimension) {
@@ -170,9 +177,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "description")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
@@ -226,8 +233,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="name", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Identifier> getNames() {
         return names = nonNullCollection(names, Identifier.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
index e81b0de..3797ccc 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
@@ -23,7 +23,6 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.content.Band;
-import org.opengis.metadata.content.SampleDimension;
 import org.opengis.metadata.content.CoverageContentType;
 import org.opengis.metadata.content.TransferFunctionType;
 import org.opengis.util.Record;
@@ -36,6 +35,12 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * The characteristic of each dimension (layer) included in the resource.
@@ -45,6 +50,14 @@
  * <div class="preformat">{@code MD_SampleDimension}
  * {@code   └─units………………………} Units of data in each dimension included in the resource.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code SampleDimension} interface.
+ * </div>
+ *
  * <h2>Terminology</h2>
  * <cite>Data values</cite> should be physical values expressed in the unit of measurement
  * given by {@link #getUnits()}. <cite>Cell values</cite> are values stored in the device,
@@ -84,7 +97,8 @@
 })
 @XmlRootElement(name = "MD_SampleDimension")
 @XmlSeeAlso({DefaultBand.class, DefaultRangeDimension.class})
-public class DefaultSampleDimension extends DefaultRangeDimension implements SampleDimension {
+@UML(identifier="MD_SampleDimension", specification=ISO_19115)
+public class DefaultSampleDimension extends DefaultRangeDimension {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -182,57 +196,50 @@
      * </div>
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(SampleDimension)
      */
-    public DefaultSampleDimension(final SampleDimension object) {
+    public DefaultSampleDimension(final DefaultSampleDimension object) {
         super(object);
         if (object != null) {
-            minValue                 = object.getMinValue();
-            maxValue                 = object.getMaxValue();
-            meanValue                = object.getMeanValue();
-            numberOfValues           = object.getNumberOfValues();
-            standardDeviation        = object.getStandardDeviation();
-            units                    = object.getUnits();
-            scaleFactor              = object.getScaleFactor();
-            offset                   = object.getOffset();
-            transferFunctionType     = object.getTransferFunctionType();
-            bitsPerValue             = object.getBitsPerValue();
-            nominalSpatialResolution = object.getNominalSpatialResolution();
-            otherPropertyType        = object.getOtherPropertyType();
-            otherProperty            = object.getOtherProperty();
+            init(object);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is is an instance of {@link Band}, then this
-     *       method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultSampleDimension}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultSampleDimension} instance is created using the
-     *       {@linkplain #DefaultSampleDimension(SampleDimension) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
+     * Initializes this sample dimension to the values of the given object.
      */
-    public static DefaultSampleDimension castOrCopy(final SampleDimension object) {
-        if (object instanceof Band) {
-            return DefaultBand.castOrCopy((Band) object);
+    private void init(final DefaultSampleDimension object) {
+        minValue                 = object.getMinValue();
+        maxValue                 = object.getMaxValue();
+        meanValue                = object.getMeanValue();
+        numberOfValues           = object.getNumberOfValues();
+        standardDeviation        = object.getStandardDeviation();
+        units                    = object.getUnits();
+        scaleFactor              = object.getScaleFactor();
+        offset                   = object.getOffset();
+        transferFunctionType     = object.getTransferFunctionType();
+        bitsPerValue             = object.getBitsPerValue();
+        nominalSpatialResolution = object.getNominalSpatialResolution();
+        otherPropertyType        = object.getOtherPropertyType();
+        otherProperty            = object.getOtherProperty();
+    }
+
+    /**
+     * Bridge constructor for {@link DefaultBand#DefaultBand(Band)}.
+     */
+    DefaultSampleDimension(final Band object) {
+        super(object);
+        if (object != null) {
+            if (object instanceof DefaultSampleDimension) {
+                init((DefaultSampleDimension) object);
+            } else {
+                maxValue     = object.getMaxValue();
+                minValue     = object.getMinValue();
+                units        = object.getUnits();
+                scaleFactor  = object.getScaleFactor();
+                offset       = object.getOffset();
+                bitsPerValue = object.getBitsPerValue();
+            }
         }
-        //-- Intentionally tested after the sub-interfaces.
-        if (object == null || object instanceof DefaultSampleDimension) {
-            return (DefaultSampleDimension) object;
-        }
-        return new DefaultSampleDimension(object);
     }
 
     /**
@@ -240,10 +247,10 @@
      *
      * @return the number of values used in a thematic classification resource, or {@code null} if none.
      */
-    @Override
     @ValueRange(minimum = 0)
     @XmlElement(name = "numberOfValues")
     @XmlJavaTypeAdapter(GO_Integer.Since2014.class)
+    @UML(identifier="numberOfValues", obligation=OPTIONAL, specification=ISO_19115)
     public Integer getNumberOfValues() {
         return numberOfValues;
     }
@@ -267,8 +274,8 @@
      *
      * @return minimum value of data values in each dimension included in the resource, or {@code null} if unspecified.
      */
-    @Override
     @XmlElement(name = "minValue")
+    @UML(identifier="minValue", obligation=OPTIONAL, specification=ISO_19115)
     public Double getMinValue() {
         return minValue;
     }
@@ -290,8 +297,8 @@
      *
      * @return maximum value of data values in each dimension included in the resource, or {@code null} if unspecified.
      */
-    @Override
     @XmlElement(name = "maxValue")
+    @UML(identifier="maxValue", obligation=OPTIONAL, specification=ISO_19115)
     public Double getMaxValue() {
         return maxValue;
     }
@@ -313,9 +320,9 @@
      *
      * @return the mean value of data values in each dimension included in the resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "meanValue")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="meanValue", obligation=OPTIONAL, specification=ISO_19115)
     public Double getMeanValue() {
         return meanValue;
     }
@@ -337,9 +344,9 @@
      *
      * @return standard deviation of data values in each dimension included in the resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "standardDeviation")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="standardDeviation", obligation=OPTIONAL, specification=ISO_19115)
     public Double getStandardDeviation() {
         return standardDeviation;
     }
@@ -360,8 +367,8 @@
      *
      * @return the units of data in the dimension, or {@code null} if unspecified.
      */
-    @Override
     @XmlElement(name = "units")
+    @UML(identifier="units", obligation=CONDITIONAL, specification=ISO_19115)
     public Unit<?> getUnits() {
         return units;
     }
@@ -381,8 +388,8 @@
      *
      * @return scale factor which has been applied to the cell value, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "scaleFactor")
+    @UML(identifier="scaleFactor", obligation=OPTIONAL, specification=ISO_19115)
     public Double getScaleFactor() {
         return scaleFactor;
     }
@@ -402,8 +409,8 @@
      *
      * @return the physical value corresponding to a cell value of zero, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "offset")
+    @UML(identifier="offset", obligation=OPTIONAL, specification=ISO_19115)
     public Double getOffset() {
         return offset;
     }
@@ -429,7 +436,6 @@
      *
      * @return type of transfer function, or {@code null}.
      */
-    @Override
     public TransferFunctionType getTransferFunctionType() {
         return transferFunctionType;
     }
@@ -451,9 +457,9 @@
      * @return maximum number of significant bits in the uncompressed representation
      *         for the value in each band of each pixel, or {@code null} if none.
      */
-    @Override
     @ValueRange(minimum = 1)
     @XmlElement(name = "bitsPerValue")
+    @UML(identifier="bitsPerValue", obligation=OPTIONAL, specification=ISO_19115)
     public Integer getBitsPerValue() {
         return bitsPerValue;
     }
@@ -484,7 +490,6 @@
      *
      * @return smallest distance between which separate points can be distinguished, or {@code null}.
      */
-    @Override
     @ValueRange(minimum = 0, isMinIncluded = false)
     public Double getNominalSpatialResolution() {
         return nominalSpatialResolution;
@@ -509,9 +514,9 @@
      *
      * @return type of other attribute description, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "otherPropertyType")
     @XmlJavaTypeAdapter(GO_RecordType.Since2014.class)
+    @UML(identifier="otherPropertyType", obligation=OPTIONAL, specification=ISO_19115)
     public RecordType getOtherPropertyType() {
         return otherPropertyType;
     }
@@ -532,9 +537,9 @@
      *
      * @return instance of other/attributeType that defines attributes, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "otherProperty")
     @XmlJavaTypeAdapter(GO_Record.Since2014.class)
+    @UML(identifier="otherProperty", obligation=OPTIONAL, specification=ISO_19115)
     public Record getOtherProperty() {
         return otherProperty;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
index 7936548..875407b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
@@ -127,11 +127,14 @@
     public DefaultDataFile(final DataFile object) {
         super(object);
         if (object != null) {
-            fileName        = object.getFileName();
-            fileDescription = object.getFileDescription();
-            fileType        = object.getFileType();
-            featureTypes    = copyCollection(object.getFeatureTypes(), LocalName.class);
-            fileFormat      = object.getFileFormat();
+            if (object instanceof DefaultDataFile) {
+                DefaultDataFile df = (DefaultDataFile) object;
+                fileName        = df.getFileName();
+                fileDescription = df.getFileDescription();
+                fileType        = df.getFileType();
+            }
+            featureTypes = copyCollection(object.getFeatureTypes(), LocalName.class);
+            fileFormat   = object.getFileFormat();
         }
     }
 
@@ -168,7 +171,6 @@
      * @see org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic#getFileName()
      * @since 1.0
      */
-    @Override
     @XmlElement(name = "fileName", required = true)
     public URI getFileName() {
         return fileName;
@@ -194,7 +196,6 @@
      * @see org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic#getFileDescription()
      * @since 1.0
      */
-    @Override
     @XmlElement(name = "fileDescription", required = true)
     public InternationalString getFileDescription() {
         return fileDescription;
@@ -220,7 +221,6 @@
      * @see org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic#getFileType()
      * @since 1.0
      */
-    @Override
     @XmlElement(name = "fileType", required = true)
     @XmlJavaTypeAdapter(MimeFileTypeAdapter.class)
     public String getFileType() {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
index 28cfcbb..b72959d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
@@ -37,6 +37,11 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Technical means and media by which a resource is obtained from the distributor.
@@ -132,9 +137,14 @@
             unitsOfDistribution = object.getUnitsOfDistribution();
             transferSize        = object.getTransferSize();
             onLines             = copyCollection(object.getOnLines(), OnlineResource.class);
-            offLines            = copyCollection(object.getOffLines(), Medium.class);
-            transferFrequency   = object.getTransferFrequency();
-            distributionFormats = copyCollection(object.getDistributionFormats(), Format.class);
+            if (object instanceof DefaultDigitalTransferOptions) {
+                final DefaultDigitalTransferOptions c = (DefaultDigitalTransferOptions) object;
+                offLines            = copyCollection(c.getOffLines(), Medium.class);
+                transferFrequency   = c.getTransferFrequency();
+                distributionFormats = copyCollection(c.getDistributionFormats(), Format.class);
+            } else {
+                offLines = singleton(object.getOffLine(), Medium.class);
+            }
         }
     }
 
@@ -238,8 +248,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "offLine")
+    @UML(identifier="offLine", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Medium> getOffLines() {
         Collection<Medium> c = offLines = nonNullCollection(offLines, Medium.class);
         if (c != null && c.size() > 1 && FilterByVersion.LEGACY_METADATA.accept()) {
@@ -293,9 +303,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "transferFrequency")
     @XmlJavaTypeAdapter(TM_PeriodDuration.Since2014.class)
+    @UML(identifier="transferFrequency", obligation=OPTIONAL, specification=ISO_19115)
     public PeriodDuration getTransferFrequency() {
         return transferFrequency;
     }
@@ -319,8 +329,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="distributionFormat", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Format> getDistributionFormats() {
         return distributionFormats = nonNullCollection(distributionFormats, Format.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java
index b57f0a7..e0a0545 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java
@@ -30,6 +30,11 @@
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.internal.jaxb.gco.InternationalStringAdapter;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the distributor of and options for obtaining the resource.
@@ -112,10 +117,12 @@
     public DefaultDistribution(final Distribution object) {
         super(object);
         if (object != null) {
-            description         = object.getDescription();
             distributionFormats = copyCollection(object.getDistributionFormats(), Format.class);
             distributors        = copyCollection(object.getDistributors(), Distributor.class);
             transferOptions     = copyCollection(object.getTransferOptions(), DigitalTransferOptions.class);
+            if (object instanceof DefaultDistribution) {
+                description = ((DefaultDistribution) object).getDescription();
+            }
         }
     }
 
@@ -151,9 +158,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "description")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultFormat.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
index b3480fe..8660d12 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
@@ -37,6 +37,12 @@
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Description of the computer language construct that specifies the representation
@@ -150,11 +156,17 @@
     public DefaultFormat(final Format object) {
         super(object);
         if (object != null) {
-            formatSpecificationCitation = object.getFormatSpecificationCitation();
             amendmentNumber             = object.getAmendmentNumber();
             fileDecompressionTechnique  = object.getFileDecompressionTechnique();
-            media                       = copyCollection(object.getMedia(), Medium.class);
             formatDistributors          = copyCollection(object.getFormatDistributors(), Distributor.class);
+            if (object instanceof DefaultFormat) {
+                formatSpecificationCitation = ((DefaultFormat) object).getFormatSpecificationCitation();
+                media = copyCollection(((DefaultFormat) object).getMedia(), Medium.class);
+            } else {
+                setSpecification(object.getSpecification());
+                setVersion(object.getVersion());
+                setName(object.getName());
+            }
         }
     }
 
@@ -190,9 +202,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "formatSpecificationCitation", required = true)
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="formatSpecificationCitation", obligation=MANDATORY, specification=ISO_19115)
     public Citation getFormatSpecificationCitation() {
         return formatSpecificationCitation;
     }
@@ -397,9 +409,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "medium")
     @XmlJavaTypeAdapter(MD_Medium.Since2014.class)
+    @UML(identifier="medium", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Medium> getMedia() {
         return media = nonNullCollection(media, Medium.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
index 685400c..7062ba2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
@@ -16,9 +16,7 @@
  */
 package org.apache.sis.metadata.iso.distribution;
 
-import java.util.AbstractSet;
 import java.util.Collection;
-import java.util.Iterator;
 import javax.measure.Unit;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
@@ -44,6 +42,12 @@
 import org.apache.sis.internal.util.CodeLists;
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
+import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
+
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -87,7 +91,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 2657393801067168091L;
+    private static final long serialVersionUID = 7751002701087451894L;
 
     /**
      * Name of the medium on which the resource can be received.
@@ -98,7 +102,7 @@
      * Density at which the data is recorded.
      * If non-null, then the number shall be greater than zero.
      */
-    private Double density;
+    private Collection<Double> densities;
 
     /**
      * Units of measure for the recording density.
@@ -146,12 +150,14 @@
         super(object);
         if (object != null) {
             name          = object.getName();
-            density       = object.getDensity();
+            densities     = copyCollection(object.getDensities(), Double.class);
             densityUnits  = object.getDensityUnits();
             volumes       = object.getVolumes();
             mediumFormats = copyCollection(object.getMediumFormats(), MediumFormat.class);
             mediumNote    = object.getMediumNote();
-            identifiers   = singleton(object.getIdentifier(), Identifier.class);
+            if (object instanceof DefaultMedium) {
+                identifiers = singleton(((DefaultMedium) object).getIdentifier(), Identifier.class);
+            }
         }
     }
 
@@ -220,12 +226,12 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "density")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
     @ValueRange(minimum = 0, isMinIncluded = false)
+    @UML(identifier="density", obligation=OPTIONAL, specification=ISO_19115)
     public Double getDensity() {
-        return density;
+        return LegacyPropertyAdapter.getSingleton(densities, Double.class, null, DefaultMedium.class, "getDensity");
     }
 
     /**
@@ -238,9 +244,9 @@
      * @since 0.5
      */
     public void setDensity(final Double newValue) {
-        checkWritePermission(density);
+        checkWritePermission(valueIfDefined(densities));
         if (ensurePositive(DefaultMedium.class, "density", true, newValue)) {
-            density = newValue;
+            densities = writeCollection(CollectionsExt.singletonOrEmpty(newValue), densities, Double.class);
         }
     }
 
@@ -255,28 +261,7 @@
     @XmlElement(name = "density", namespace = LegacyNamespaces.GMD)
     public Collection<Double> getDensities() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new AbstractSet<Double>() {
-            /** Returns 0 if empty, or 1 if a density has been specified. */
-            @Override public int size() {
-                return getDensity() != null ? 1 : 0;
-            }
-
-            /** Returns an iterator over 0 or 1 element. Current iterator implementation is unmodifiable. */
-            @Override public Iterator<Double> iterator() {
-                return CollectionsExt.singletonOrEmpty(getDensity()).iterator();
-            }
-
-            /** Adds an element only if the set is empty. This method is invoked by JAXB at unmarshalling time. */
-            @Override public boolean add(final Double newValue) {
-                if (isEmpty()) {
-                    setDensity(newValue);
-                    return true;
-                } else {
-                    LegacyPropertyAdapter.warnIgnoredExtraneous(Double.class, DefaultMedium.class, "setDensities");
-                    return false;
-                }
-            }
-        };
+        return densities = nonNullCollection(densities, Double.class);
     }
 
     /**
@@ -286,7 +271,7 @@
      */
     @Deprecated
     public void setDensities(final Collection<? extends Double> newValues) {
-        setDensity(LegacyPropertyAdapter.getSingleton(newValues, Double.class, null, DefaultMedium.class, "setDensities"));
+        densities = writeCollection(newValues, densities, Double.class);
     }
 
     /**
@@ -383,9 +368,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "identifier")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="identifier", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getIdentifier() {
         return super.getIdentifier();
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
index be37b40..35a3c66 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
@@ -30,6 +30,10 @@
 import org.apache.sis.internal.jaxb.gco.GO_Record;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toDate;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toMilliseconds;
 
@@ -129,8 +133,10 @@
             plannedAvailableDateTime = toMilliseconds(object.getPlannedAvailableDateTime());
             orderingInstructions     = object.getOrderingInstructions();
             turnaround               = object.getTurnaround();
-            orderOptionsType         = object.getOrderOptionsType();
-            orderOptions             = object.getOrderOptions();
+            if (object instanceof DefaultStandardOrderProcess) {
+                orderOptionsType = ((DefaultStandardOrderProcess) object).getOrderOptionsType();
+                orderOptions     = ((DefaultStandardOrderProcess) object).getOrderOptions();
+            }
         }
     }
 
@@ -202,7 +208,6 @@
      *
      * @see #getFees()
      */
-    @Override
     public Currency getCurrency() {
         return currency;
     }
@@ -295,9 +300,9 @@
      *
      * @see org.apache.sis.util.iso.DefaultRecord#getRecordType()
      */
-    @Override
     @XmlElement(name = "orderOptionsType")
     @XmlJavaTypeAdapter(GO_RecordType.Since2014.class)
+    @UML(identifier="orderOptionsType", obligation=OPTIONAL, specification=ISO_19115)
     public RecordType getOrderOptionsType() {
         return orderOptionsType;
     }
@@ -309,7 +314,7 @@
      *
      * @since 0.5
      */
-    @Deprecated
+//  @Deprecated - omitted for allowing APIVerifier to pass.
     public RecordType getOrderOptionType() {
         return getOrderOptionsType();
     }
@@ -349,9 +354,9 @@
      *       when he ordered the resource. We presume that this is not a record to be filled by the user for new
      *       orders, otherwise this method would need to be a factory rather than a getter.
      */
-    @Override
     @XmlElement(name = "orderOptions")
     @XmlJavaTypeAdapter(GO_Record.Since2014.class)
+    @UML(identifier="orderOptions", obligation=OPTIONAL, specification=ISO_19115)
     public Record getOrderOptions() {
         return orderOptions;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
index e0b8410..c291138 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
@@ -29,6 +29,10 @@
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.metadata.ReferencingServices;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
 
 
@@ -113,7 +117,9 @@
         super(object);
         if (object != null) {
             spatialExtent  = copyCollection(object.getSpatialExtent(), GeographicExtent.class);
-            verticalExtent = object.getVerticalExtent();
+            if (object instanceof DefaultSpatialTemporalExtent) {
+                verticalExtent = ((DefaultSpatialTemporalExtent) object).getVerticalExtent();
+            }
         }
     }
 
@@ -169,8 +175,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "verticalExtent")
+    @UML(identifier="verticalExtent", obligation=OPTIONAL, specification=ISO_19115)
     public VerticalExtent getVerticalExtent() {
         return verticalExtent;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
index ecec2e8..a02771f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
@@ -22,8 +22,8 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.geometry.Envelope;
-import org.opengis.temporal.Period;
-import org.opengis.temporal.Instant;
+import org.apache.sis.internal.geoapi.temporal.Period;
+import org.apache.sis.internal.geoapi.temporal.Instant;
 import org.opengis.temporal.TemporalPrimitive;
 import org.opengis.metadata.extent.TemporalExtent;
 import org.opengis.metadata.extent.SpatialTemporalExtent;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
index a2fe0f3..c915b5e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
@@ -25,7 +25,6 @@
 import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.metadata.extent.VerticalExtent;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.internal.jaxb.gco.GO_Real;
@@ -272,14 +271,14 @@
      * bounds, then the corresponding bounds of the intersection result will also be NaN.</p>
      *
      * @param  other  the vertical extent to intersect with this extent.
-     * @throws MismatchedReferenceSystemException if the two extents do not use the same datum, ignoring metadata.
+     * @throws IllegalArgumentException if the two extents do not use the same datum, ignoring metadata.
      *
      * @see Extents#intersection(VerticalExtent, VerticalExtent)
      * @see org.apache.sis.geometry.GeneralEnvelope#intersect(Envelope)
      *
      * @since 0.8
      */
-    public void intersect(final VerticalExtent other) throws MismatchedReferenceSystemException {
+    public void intersect(final VerticalExtent other) throws IllegalArgumentException {
         checkWritePermission(value());
         ArgumentChecks.ensureNonNull("other", other);
         Double min = other.getMinimumValue();
@@ -307,7 +306,7 @@
                 }
             }
         } catch (UnsupportedOperationException | FactoryException | ClassCastException | TransformException e) {
-            throw new MismatchedReferenceSystemException(Errors.format(Errors.Keys.IncompatiblePropertyValue_1, "verticalCRS"), e);
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatiblePropertyValue_1, "verticalCRS"), e);
         }
         if (minimumValue != null && maximumValue != null && minimumValue > maximumValue) {
             minimumValue = maximumValue = NilReason.MISSING.createNilObject(Double.class);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
index 4ab7000..f1b85a3 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
@@ -35,7 +35,6 @@
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.extent.GeographicDescription;
 import org.opengis.metadata.identification.Identification;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.crs.VerticalCRS;
@@ -63,6 +62,8 @@
 
 // Branch-dependent imports
 import org.opengis.geometry.Geometry;
+import org.opengis.metadata.identification.DataIdentification;
+import org.apache.sis.internal.geoapi.evolution.Interim;
 
 
 /**
@@ -141,9 +142,11 @@
         final Extents m = new Extents();
         try {
             for (final Identification id : nonNull(metadata.getIdentificationInfo())) {
-                if (id != null) for (final Extent extent : nonNull(id.getExtents())) {
-                    if (extent != null) {
-                        m.addHorizontal(extent);
+                if (id instanceof DataIdentification) {
+                    for (final Extent extent : nonNull(((DataIdentification) id).getExtents())) {
+                        if (extent != null) {
+                            m.addHorizontal(extent);
+                        }
                     }
                 }
             }
@@ -232,7 +235,7 @@
                  */
                 if (!Boolean.FALSE.equals(element.getInclusion())) {
                     for (final Geometry geometry : nonNull(((BoundingPolygon) extent).getPolygons())) {
-                        final Envelope envelope = geometry.getEnvelope();
+                        final Envelope envelope = Interim.getEnvelope(geometry);
                         if (envelope != null) {
                             final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
                             if (crs != null) {
@@ -629,7 +632,7 @@
      * @param  e2  the second extent, or {@code null}.
      * @return the intersection (may be any of the {@code e1} or {@code e2} argument if unchanged),
      *         or {@code null} if the two given extents are null.
-     * @throws MismatchedReferenceSystemException if the two extents do not use the same datum, ignoring metadata.
+     * @throws IllegalArgumentException if the two extents do not use the same datum, ignoring metadata.
      *
      * @see DefaultVerticalExtent#intersect(VerticalExtent)
      *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
index 9faa069..bad2442 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.metadata.iso.identification;
 
+import java.util.List;
+import java.util.ArrayList;
 import java.util.Collection;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlSeeAlso;
@@ -29,7 +31,6 @@
 import org.opengis.metadata.distribution.Format;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.identification.AggregateInformation;
-import org.opengis.metadata.identification.AssociatedResource;
 import org.opengis.metadata.identification.Identification;
 import org.opengis.metadata.identification.DataIdentification;
 import org.opengis.metadata.identification.BrowseGraphic;
@@ -41,7 +42,6 @@
 import org.opengis.metadata.identification.ServiceIdentification;
 import org.opengis.metadata.maintenance.MaintenanceInformation;
 import org.opengis.metadata.spatial.SpatialRepresentationType;
-import org.opengis.temporal.Duration;
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.metadata.Dependencies;
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
@@ -51,6 +51,12 @@
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Basic information required to uniquely identify a resource or resources.
@@ -95,7 +101,6 @@
     "pointOfContacts",
     "spatialRepresentationTypes",       // Here in ISO 19115:2014 (was after 'aggregationInfo' in ISO 19115:2003)
     "spatialResolutions",               // Shall be kept next to 'spatialRepresentationTypes'
-    "temporalResolution",               // ISO 19115-3 only
     "topicCategories",                  // Here in ISO 19115:2014 (was in subclasses in ISO 19115:2003)
     "extents",                          // Here in ISO 19115:2014 (was in subclasses in ISO 19115:2003)
     "additionalDocumentation",          // ISO 19115:2014 only
@@ -124,7 +129,7 @@
     /**
      * Serial number for compatibility with different versions.
      */
-    private static final long serialVersionUID = -1132210324047663554L;
+    private static final long serialVersionUID = 157053637951213015L;
 
     /**
      * Citation for the resource(s).
@@ -168,11 +173,6 @@
     private Collection<Resolution> spatialResolutions;
 
     /**
-     * Smallest resolvable temporal period in a resource.
-     */
-    private Collection<Duration> temporalResolutions;
-
-    /**
      * Main theme(s) of the resource.
      */
     private Collection<TopicCategory> topicCategories;
@@ -226,7 +226,7 @@
     /**
      * Provides aggregate dataset information.
      */
-    private Collection<AssociatedResource> associatedResources;
+    private Collection<DefaultAssociatedResource> associatedResources;
 
     /**
      * Constructs an initially empty identification.
@@ -263,20 +263,24 @@
             credits                    = copyCollection(object.getCredits(), String.class);
             status                     = copyCollection(object.getStatus(), Progress.class);
             pointOfContacts            = copyCollection(object.getPointOfContacts(), ResponsibleParty.class);
-            spatialRepresentationTypes = copyCollection(object.getSpatialRepresentationTypes(), SpatialRepresentationType.class);
-            spatialResolutions         = copyCollection(object.getSpatialResolutions(), Resolution.class);
-            temporalResolutions        = copyCollection(object.getTemporalResolutions(), Duration.class);
-            topicCategories            = copyCollection(object.getTopicCategories(), TopicCategory.class);
-            extents                    = copyCollection(object.getExtents(), Extent.class);
-            additionalDocumentations   = copyCollection(object.getAdditionalDocumentations(), Citation.class);
-            processingLevel            = object.getProcessingLevel();
             resourceMaintenances       = copyCollection(object.getResourceMaintenances(), MaintenanceInformation.class);
             graphicOverviews           = copyCollection(object.getGraphicOverviews(), BrowseGraphic.class);
             resourceFormats            = copyCollection(object.getResourceFormats(), Format.class);
             descriptiveKeywords        = copyCollection(object.getDescriptiveKeywords(), Keywords.class);
             resourceSpecificUsages     = copyCollection(object.getResourceSpecificUsages(), Usage.class);
             resourceConstraints        = copyCollection(object.getResourceConstraints(), Constraints.class);
-            associatedResources        = copyCollection(object.getAssociatedResources(), AssociatedResource.class);
+            if (object instanceof AbstractIdentification) {
+                final AbstractIdentification c = (AbstractIdentification) object;
+                spatialRepresentationTypes = copyCollection(c.getSpatialRepresentationTypes(), SpatialRepresentationType.class);
+                spatialResolutions         = copyCollection(c.getSpatialResolutions(), Resolution.class);
+                topicCategories            = copyCollection(c.getTopicCategories(), TopicCategory.class);
+                extents                    = copyCollection(c.getExtents(), Extent.class);
+                additionalDocumentations   = copyCollection(c.getAdditionalDocumentations(), Citation.class);
+                processingLevel            = c.getProcessingLevel();
+                associatedResources        = copyCollection(c.getAssociatedResources(), DefaultAssociatedResource.class);
+            } else {
+                setAggregationInfo(object.getAggregationInfo());
+            }
         }
     }
 
@@ -468,8 +472,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "spatialRepresentationType")
+    @UML(identifier="spatialRepresentationType", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<SpatialRepresentationType> getSpatialRepresentationTypes() {
         return spatialRepresentationTypes = nonNullCollection(spatialRepresentationTypes, SpatialRepresentationType.class);
     }
@@ -493,8 +497,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "spatialResolution")
+    @UML(identifier="spatialResolution", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Resolution> getSpatialResolutions() {
         return spatialResolutions = nonNullCollection(spatialResolutions, Resolution.class);
     }
@@ -511,38 +515,14 @@
     }
 
     /**
-     * Returns the smallest resolvable temporal period in a resource.
-     *
-     * @return smallest resolvable temporal period in a resource.
-     *
-     * @since 0.5
-     */
-    @Override
-    // @XmlElement at the end of this class.
-    public Collection<Duration> getTemporalResolutions() {
-        return temporalResolutions = nonNullCollection(temporalResolutions, Duration.class);
-    }
-
-    /**
-     * Sets the smallest resolvable temporal period in a resource.
-     *
-     * @param  newValues  the new temporal resolutions.
-     *
-     * @since 0.5
-     */
-    public void setTemporalResolutions(final Collection<? extends Duration> newValues) {
-        temporalResolutions = writeCollection(newValues, temporalResolutions, Duration.class);
-    }
-
-    /**
      * Returns the main theme(s) of the resource.
      *
      * @return main theme(s).
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "topicCategory")
+    @UML(identifier="topicCategory", obligation=CONDITIONAL, specification=ISO_19115)
     public Collection<TopicCategory> getTopicCategories()  {
         return topicCategories = nonNullCollection(topicCategories, TopicCategory.class);
     }
@@ -565,8 +545,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "extent")
+    @UML(identifier="extent", obligation=CONDITIONAL, specification=ISO_19115)
     public Collection<Extent> getExtents() {
         return extents = nonNullCollection(extents, Extent.class);
     }
@@ -589,8 +569,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="additionalDocumentation", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAdditionalDocumentations() {
         return additionalDocumentations = nonNullCollection(additionalDocumentations, Citation.class);
     }
@@ -613,9 +593,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "processingLevel")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="processingLevel", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getProcessingLevel() {
         return processingLevel;
     }
@@ -761,25 +741,35 @@
     /**
      * Provides associated resource information.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AssociatedResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return associated resource information.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<AssociatedResource> getAssociatedResources() {
-        return associatedResources = nonNullCollection(associatedResources, AssociatedResource.class);
+    @UML(identifier="associatedResource", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultAssociatedResource> getAssociatedResources() {
+        return associatedResources = nonNullCollection(associatedResources, DefaultAssociatedResource.class);
     }
 
     /**
      * Sets associated resource information.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AssociatedResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new associated resources.
      *
      * @since 0.5
      */
-    public void setAssociatedResources(final Collection<? extends AssociatedResource> newValues) {
-        associatedResources = writeCollection(newValues, associatedResources, AssociatedResource.class);
+    public void setAssociatedResources(final Collection<? extends DefaultAssociatedResource> newValues) {
+        associatedResources = writeCollection(newValues, associatedResources, DefaultAssociatedResource.class);
     }
 
     /**
@@ -795,16 +785,20 @@
     @XmlElement(name = "aggregationInfo", namespace = LegacyNamespaces.GMD)
     public Collection<AggregateInformation> getAggregationInfo() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new LegacyPropertyAdapter<AggregateInformation,AssociatedResource>(getAssociatedResources()) {
-            @Override protected AssociatedResource wrap(final AggregateInformation value) {
-                return value;
+        return new LegacyPropertyAdapter<AggregateInformation,DefaultAssociatedResource>(getAssociatedResources()) {
+            @Override protected DefaultAssociatedResource wrap(final AggregateInformation value) {
+                return DefaultAssociatedResource.castOrCopy(value);
             }
 
-            @Override protected AggregateInformation unwrap(final AssociatedResource container) {
-                return DefaultAggregateInformation.castOrCopy(container);
+            @Override protected AggregateInformation unwrap(final DefaultAssociatedResource container) {
+                if (container instanceof AggregateInformation) {
+                    return (AggregateInformation) container;
+                } else {
+                    return new DefaultAggregateInformation(container);
+                }
             }
 
-            @Override protected boolean update(final AssociatedResource container, final AggregateInformation value) {
+            @Override protected boolean update(final DefaultAssociatedResource container, final AggregateInformation value) {
                 return container == value;
             }
         }.validOrNull();
@@ -819,7 +813,20 @@
      */
     @Deprecated
     public void setAggregationInfo(final Collection<? extends AggregateInformation> newValues) {
-        setAssociatedResources(newValues);
+        checkWritePermission(associatedResources);
+        /*
+         * We can not invoke getAggregationInfo().setValues(newValues) because this method
+         * is invoked by the constructor, which is itself invoked at JAXB marshalling time,
+         * in which case getAggregationInfo() may return null.
+         */
+        List<DefaultAssociatedResource> r = null;
+        if (newValues != null) {
+            r = new ArrayList<DefaultAssociatedResource>(newValues.size());
+            for (final AggregateInformation value : newValues) {
+                r.add(DefaultAssociatedResource.castOrCopy(value));
+            }
+        }
+        setAssociatedResources(r);
     }
 
 
@@ -840,22 +847,14 @@
      * Invoked by JAXB at both marshalling and unmarshalling time.
      * This attribute has been added by ISO 19115:2014 standard.
      * If (and only if) marshalling an older standard version, we omit this attribute.
-     *
-     * @todo Currently, the {@code XmlJavaTypeAdapter} used here just internally converts {@code Duration} objects
-     *       into {@code PeriodDuration} objects. Need to add support for {@code IntervalLength} in the future.
      */
-    @XmlElement(name = "temporalResolution")
-    private Collection<Duration> getTemporalResolution() {
-        return FilterByVersion.CURRENT_METADATA.accept() ? getTemporalResolutions() : null;
-    }
-
     @XmlElement(name = "additionalDocumentation")
     private Collection<Citation> getAdditionalDocumentation() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getAdditionalDocumentations() : null;
     }
 
     @XmlElement(name = "associatedResource")
-    private Collection<AssociatedResource> getAssociatedResource() {
+    private Collection<DefaultAssociatedResource> getAssociatedResource() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getAssociatedResources() : null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
index e0fa384..0c5314d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
@@ -26,7 +26,6 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.identification.AggregateInformation;
-import org.opengis.metadata.identification.AssociatedResource;
 import org.opengis.metadata.identification.AssociationType;
 import org.opengis.metadata.identification.InitiativeType;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
@@ -37,7 +36,7 @@
 
 
 /**
- * Aggregate dataset information.
+ * Associated resource information.
  * The following properties are mandatory or conditional (i.e. mandatory under some circumstances)
  * in a well-formed metadata according ISO 19115:
  *
@@ -48,8 +47,14 @@
  * {@code   │   └─date……………………………} Reference date for the cited resource.
  * {@code   └─name………………………………………} Citation information about the associated resource.</div>
  *
- * According ISO 19115, at least one of {@linkplain #getAggregateDataSetName() aggregate dataset name}
- * and {@linkplain #getAggregateDataSetIdentifier() aggregate dataset identifier} shall be provided.
+ * According ISO 19115, at least one of {@linkplain #getName() name} and
+ * {@linkplain #getMetadataReference() metadata reference} shall be provided.
+ *
+ * <div class="warning"><b>Upcoming API change — renaming</b><br>
+ * As of ISO 19115:2014, {@code AggregateInformation} has been renamed {@code AssociatedResource}.
+ * This class will be replaced by {@link DefaultAssociatedResource} when GeoAPI will provide the
+ * {@code AssociatedResource} interface (tentatively in GeoAPI 3.1 or 4.0).
+ * </div>
  *
  * <h2>Limitations</h2>
  * <ul>
@@ -66,10 +71,7 @@
  * @version 1.0
  * @since   0.3
  * @module
- *
- * @deprecated As of ISO 19115:2014, replaced by {@link DefaultAssociatedResource}.
  */
-@Deprecated
 @XmlType(name = "MD_AggregateInformation_Type", namespace = LegacyNamespaces.GMD, propOrder = {
     "aggregateDataSetName",
     "aggregateDataSetIdentifier",
@@ -91,15 +93,26 @@
 
     /**
      * Constructs a new instance initialized with the values from the specified metadata object.
+     *
+     * @param object The metadata to copy values from.
+     */
+    DefaultAggregateInformation(final DefaultAssociatedResource object) {
+        super(object);
+    }
+
+    /**
+     * Constructs a new instance initialized with the values from the specified metadata object.
      * This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(AssociatedResource)
      */
-    public DefaultAggregateInformation(final AssociatedResource object) {
+    public DefaultAggregateInformation(final AggregateInformation object) {
         super(object);
+        if (object != null && !(object instanceof DefaultAssociatedResource)) {
+            setAggregateDataSetName(object.getAggregateDataSetName());
+            setAggregateDataSetIdentifier(object.getAggregateDataSetIdentifier());
+        }
     }
 
     /**
@@ -111,7 +124,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code DefaultAggregateInformation}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code DefaultAggregateInformation} instance is created using the
-     *       {@linkplain #DefaultAggregateInformation(AssociatedResource) copy constructor}
+     *       copy constructor
      *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -120,7 +133,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultAggregateInformation castOrCopy(final AssociatedResource object) {
+    public static DefaultAggregateInformation castOrCopy(final AggregateInformation object) {
         if (object == null || object instanceof DefaultAggregateInformation) {
             return (DefaultAggregateInformation) object;
         }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
index e045595..0131685 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
@@ -21,7 +21,7 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.identification.AssociatedResource;
+import org.opengis.metadata.identification.AggregateInformation;
 import org.opengis.metadata.identification.AssociationType;
 import org.opengis.metadata.identification.InitiativeType;
 import org.apache.sis.internal.jaxb.metadata.CI_Citation;
@@ -29,6 +29,13 @@
 import org.apache.sis.internal.jaxb.code.DS_InitiativeTypeCode;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Branch-specific imports.
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Associated resource information.
@@ -68,7 +75,8 @@
     "metadataReference"
 })
 @XmlRootElement(name = "MD_AssociatedResource")
-public class DefaultAssociatedResource extends ISOMetadata implements AssociatedResource {
+@UML(identifier="MD_AssociatedResource", specification=ISO_19115)
+public class DefaultAssociatedResource extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -113,41 +121,43 @@
 
     /**
      * Constructs a new instance initialized with the values from the specified metadata object.
+     * This is a constructor for {@link DefaultAggregateInformation} constructor only.
+     *
+     * @param object The metadata to copy values from.
+     */
+    DefaultAssociatedResource(final DefaultAssociatedResource object) {
+        this.associationType   = object.associationType;
+        this.initiativeType    = object.initiativeType;
+        this.name              = object.name;
+        this.metadataReference = object.metadataReference;
+    }
+
+    /**
+     * Constructs a new instance initialized with the values from the specified metadata object.
      * This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(AssociatedResource)
      */
-    public DefaultAssociatedResource(final AssociatedResource object) {
+    DefaultAssociatedResource(final AggregateInformation object) {
         if (object != null) {
-            this.name              = object.getName();
             this.associationType   = object.getAssociationType();
             this.initiativeType    = object.getInitiativeType();
-            this.metadataReference = object.getMetadataReference();
+            if (object instanceof DefaultAssociatedResource) {
+                this.name              = ((DefaultAssociatedResource) object).getName();
+                this.metadataReference = ((DefaultAssociatedResource) object).getMetadataReference();
+            }
         }
     }
 
     /**
      * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultAssociatedResource}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultAssociatedResource} instance is created using the
-     *       {@linkplain #DefaultAssociatedResource(AssociatedResource) copy constructor} and returned.
-     *       Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
      *
      * @param  object  the object to get as a SIS implementation, or {@code null} if none.
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultAssociatedResource castOrCopy(final AssociatedResource object) {
+    static DefaultAssociatedResource castOrCopy(final AggregateInformation object) {
         if (object == null || object instanceof DefaultAssociatedResource) {
             return (DefaultAssociatedResource) object;
         }
@@ -159,9 +169,9 @@
      *
      * @return Citation information about the associated resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "name")
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="name", obligation=CONDITIONAL, specification=ISO_19115)
     public Citation getName() {
         return name;
     }
@@ -181,9 +191,9 @@
      *
      * @return type of relation between the resources.
      */
-    @Override
     @XmlElement(name = "associationType", required = true)
     @XmlJavaTypeAdapter(DS_AssociationTypeCode.Since2014.class)
+    @UML(identifier="associationType", obligation=MANDATORY, specification=ISO_19115)
     public AssociationType getAssociationType() {
         return associationType;
     }
@@ -203,9 +213,9 @@
      *
      * @return the type of initiative under which the associated resource was produced, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "initiativeType")
     @XmlJavaTypeAdapter(DS_InitiativeTypeCode.Since2014.class)
+    @UML(identifier="initiativeType", obligation=OPTIONAL, specification=ISO_19115)
     public InitiativeType getInitiativeType() {
         return initiativeType;
     }
@@ -225,9 +235,9 @@
      *
      * @return reference to the metadata of the associated resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "metadataReference")
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="metadataReference", obligation=CONDITIONAL, specification=ISO_19115)
     public Citation getMetadataReference() {
         return metadataReference;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java
index 55c2784..8836f0e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java
@@ -31,6 +31,11 @@
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Graphic that provides an illustration of the dataset (should include a legend for the graphic).
@@ -127,8 +132,10 @@
             fileName         = object.getFileName();
             fileDescription  = object.getFileDescription();
             fileType         = object.getFileType();
-            imageConstraints = copyCollection(object.getImageConstraints(), Constraints.class);
-            linkages         = copyCollection(object.getLinkages(), OnlineResource.class);
+            if (object instanceof DefaultBrowseGraphic) {
+                imageConstraints = copyCollection(((DefaultBrowseGraphic) object).getImageConstraints(), Constraints.class);
+                linkages         = copyCollection(((DefaultBrowseGraphic) object).getLinkages(), OnlineResource.class);
+            }
         }
     }
 
@@ -234,8 +241,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="imageContraints", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Constraints> getImageConstraints() {
         return imageConstraints = nonNullCollection(imageConstraints, Constraints.class);
     }
@@ -258,8 +265,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="linkage", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getLinkages() {
         return linkages = nonNullCollection(linkages, OnlineResource.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
index bda39dc..7e646ea 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
@@ -25,8 +25,6 @@
 import org.opengis.util.ScopedName;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.identification.DataIdentification;
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.OperationMetadata;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.internal.jaxb.metadata.SV_OperationMetadata;
 import org.apache.sis.internal.jaxb.FilterByVersion;
@@ -36,10 +34,23 @@
 import org.apache.sis.util.iso.Names;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Links a given operation name with a resource identified by an "identifier".
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code CoupledResource} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -66,7 +77,8 @@
     "legacyName"                // Legacy ISO 19139:2007 way to write scoped name
 })
 @XmlRootElement(name = "SV_CoupledResource", namespace = Namespaces.SRV)
-public class DefaultCoupledResource extends ISOMetadata implements CoupledResource {
+@UML(identifier="SV_CoupledResource", specification=ISO_19115)
+public class DefaultCoupledResource extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -90,7 +102,7 @@
     /**
      * The service operation.
      */
-    private OperationMetadata operation;
+    private DefaultOperationMetadata operation;
 
     /**
      * Constructs an initially empty coupled resource.
@@ -109,7 +121,7 @@
     public DefaultCoupledResource(final ScopedName name,
                                   final Citation reference,
                                   final DataIdentification resource,
-                                  final OperationMetadata operation)
+                                  final DefaultOperationMetadata operation)
     {
         this.scopedName         = name;
         this.resourceReferences = singleton(reference, Citation.class);
@@ -123,10 +135,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(CoupledResource)
      */
-    public DefaultCoupledResource(final CoupledResource object) {
+    public DefaultCoupledResource(final DefaultCoupledResource object) {
         super(object);
         if (object != null) {
             this.scopedName         = object.getScopedName();
@@ -137,38 +147,13 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultCoupledResource}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultCoupledResource} instance is created using the
-     *       {@linkplain #DefaultCoupledResource(CoupledResource) copy constructor} and returned.
-     *       Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultCoupledResource castOrCopy(final CoupledResource object) {
-        if (object == null || object instanceof DefaultCoupledResource) {
-            return (DefaultCoupledResource) object;
-        }
-        return new DefaultCoupledResource(object);
-    }
-
-    /**
      * Returns scoped identifier of the resource in the context of the given service instance.
      *
      * @return identifier of the resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "scopedName")
     @XmlJavaTypeAdapter(GO_GenericName.Since2014.class)
+    @UML(identifier="scopedName", obligation=OPTIONAL, specification=ISO_19115)
     public ScopedName getScopedName() {
         return scopedName;
     }
@@ -188,8 +173,8 @@
      *
      * @return references to the resource on which the services operates.
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="resourceReference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getResourceReferences() {
         return resourceReferences = nonNullCollection(resourceReferences, Citation.class);
     }
@@ -208,8 +193,8 @@
      *
      * @return tightly coupled resources.
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="resource", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<DataIdentification> getResources() {
         return resources = nonNullCollection(resources, DataIdentification.class);
     }
@@ -226,23 +211,33 @@
     /**
      * Returns the service operation.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The return type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return the service operation, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "operation")
     @XmlJavaTypeAdapter(SV_OperationMetadata.Since2014.class)
-    public OperationMetadata getOperation() {
+    @UML(identifier="operation", obligation=OPTIONAL, specification=ISO_19115)
+    public DefaultOperationMetadata getOperation() {
         return operation;
     }
 
     /**
      * Sets a new service operation.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The argument type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValue  the new service operation.
      */
-    public void setOperation(final OperationMetadata newValue) {
+    public void setOperation(final DefaultOperationMetadata newValue) {
         checkWritePermission(operation);
-        operation = newValue;
+        this.operation = newValue;
     }
 
 
@@ -265,7 +260,7 @@
     @XmlElement(name = "operationName", namespace = LegacyNamespaces.SRV)
     private String getOperationName() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            final OperationMetadata operation = getOperation();
+            final DefaultOperationMetadata operation = getOperation();
             if (operation != null) {
                 return operation.getOperationName();
             }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
index d8bd253..07743fd 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
@@ -37,6 +37,12 @@
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.metadata.Dependencies;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.internal.jaxb.code.MD_CharacterSetCode;
+
 
 /**
  * Information required to identify a dataset.
@@ -152,7 +158,11 @@
     public DefaultDataIdentification(final DataIdentification object) {
         super(object);
         if (object != null) {
-            locales                 = copyMap(object.getLocalesAndCharsets(), Locale.class);
+            if (object instanceof DefaultDataIdentification) {
+                locales = copyMap(((DefaultDataIdentification) object).getLocalesAndCharsets(), Locale.class);
+            } else {
+                setLanguages(object.getLanguages());
+            }
             environmentDescription  = object.getEnvironmentDescription();
             supplementalInformation = object.getSupplementalInformation();
         }
@@ -192,7 +202,7 @@
      *
      * @since 1.0
      */
-    @Override
+    @UML(identifier="defaultLocale+otherLocale", obligation=CONDITIONAL, specification=ISO_19115)
     // @XmlElement at the end of this class.
     public Map<Locale,Charset> getLocalesAndCharsets() {
         return locales = nonNullMap(locales, Locale.class);
@@ -260,7 +270,7 @@
     @Dependencies("getLocalesAndCharsets")
     // @XmlElement at the end of this class.
     public Collection<CharacterSet> getCharacterSets() {
-        return getLocalesAndCharsets().values().stream().map(CharacterSet::fromCharset).collect(Collectors.toSet());
+        return getLocalesAndCharsets().values().stream().map(MD_CharacterSetCode::fromCharset).collect(Collectors.toSet());
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java
index ae8f1bc..ba54d53 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java
@@ -22,7 +22,6 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.identification.KeywordClass;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.util.iso.Types;
@@ -39,6 +38,14 @@
  * {@code       ├─title………………………} Name by which the cited resource is known.
  * {@code       └─date…………………………} Reference date for the cited resource.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code KeywordClass} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -60,7 +67,7 @@
     "ontology"
 })
 @XmlRootElement(name = "MD_KeywordClass")
-public class DefaultKeywordClass extends ISOMetadata implements KeywordClass {
+public class DefaultKeywordClass extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -105,10 +112,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(KeywordClass)
      */
-    public DefaultKeywordClass(final KeywordClass object) {
+    public DefaultKeywordClass(final DefaultKeywordClass object) {
         super(object);
         if (object != null) {
             className         = object.getClassName();
@@ -118,36 +123,10 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultKeywordClass}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultKeywordClass} instance is created using the
-     *       {@linkplain #DefaultKeywordClass(KeywordClass) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultKeywordClass castOrCopy(final KeywordClass object) {
-        if (object == null || object instanceof DefaultKeywordClass) {
-            return (DefaultKeywordClass) object;
-        }
-        return new DefaultKeywordClass(object);
-    }
-
-    /**
      * Returns a label for the keyword category in natural language.
      *
      * @return the keyword category in natural language.
      */
-    @Override
     @XmlElement(name = "className", required = true)
     public InternationalString getClassName() {
         return className;
@@ -168,7 +147,6 @@
      *
      * @return URI of concept in the ontology, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "conceptIdentifier")
     public URI getConceptIdentifier() {
         return conceptIdentifier;
@@ -189,7 +167,6 @@
      *
      * @return a reference that binds the keyword class to a formal conceptualization.
      */
-    @Override
     @XmlElement(name = "ontology", required = true)
     public Citation getOntology() {
         return ontology;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
index 1d91bb6..c086746 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
@@ -25,11 +25,15 @@
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.identification.Keywords;
 import org.opengis.metadata.identification.KeywordType;
-import org.opengis.metadata.identification.KeywordClass;
 import org.apache.sis.internal.jaxb.metadata.MD_KeywordClass;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Keywords, their type and reference source.
@@ -87,7 +91,7 @@
      * User-defined categorization of groups of keywords that extend or are orthogonal
      * to the standardized {@linkplain #getType() keyword type} codes.
      */
-    private KeywordClass keywordClass;
+    private DefaultKeywordClass keywordClass;
 
     /**
      * Constructs an initially empty keywords.
@@ -224,25 +228,34 @@
      * Returns the user-defined categorization of groups of keywords that extend or
      * are orthogonal to the standardized {@linkplain #getType() keyword type} codes.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code KeywordClass} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return user-defined categorization of groups of keywords, or {@code null} if none.
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "keywordClass")
     @XmlJavaTypeAdapter(MD_KeywordClass.Since2014.class)
-    public KeywordClass getKeywordClass() {
+    @UML(identifier="keywordClass", obligation=OPTIONAL, specification=ISO_19115)
+    public DefaultKeywordClass getKeywordClass() {
         return keywordClass;
     }
 
     /**
      * Sets the user-defined categorization of groups of keywords.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The argument type will be changed to the {@code KeywordClass} interface when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1).</div>
+     *
      * @param newValue  new user-defined categorization of groups of keywords.
      *
      * @since 0.5
      */
-    public void setKeywordClass(final KeywordClass newValue) {
+    public void setKeywordClass(final DefaultKeywordClass newValue) {
         checkWritePermission(keywordClass);
         keywordClass = newValue;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
index d36a6f6..d6130a9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
@@ -20,14 +20,18 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
-import org.opengis.metadata.identification.OperationChainMetadata;
-import org.opengis.metadata.identification.OperationMetadata;
 import org.opengis.util.InternationalString;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Operation chain information.
@@ -41,6 +45,14 @@
  * {@code       └─connectPoint………………………………………………} Handle for accessing the service interface.
  * {@code           └─linkage…………………………………………………} Location for on-line access using a URL address or similar addressing scheme.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code OperationChainMetadata} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -63,11 +75,12 @@
     "operations"
 })
 @XmlRootElement(name = "SV_OperationChainMetadata", namespace = Namespaces.SRV)
-public class DefaultOperationChainMetadata extends ISOMetadata implements OperationChainMetadata {
+@UML(identifier="SV_OperationChainMetadata", specification=ISO_19115)
+public class DefaultOperationChainMetadata extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
-    private static final long serialVersionUID = 4132508877114835287L;
+    private static final long serialVersionUID = 4132508877114835286L;
 
     /**
      * The name as used by the service for this chain.
@@ -82,7 +95,7 @@
     /**
      * Information about the operations applied by the chain.
      */
-    private List<OperationMetadata> operations;
+    private List<DefaultOperationMetadata> operations;
 
     /**
      * Constructs an initially empty operation chain metadata.
@@ -105,50 +118,23 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(OperationChainMetadata)
      */
-    public DefaultOperationChainMetadata(final OperationChainMetadata object) {
+    public DefaultOperationChainMetadata(final DefaultOperationChainMetadata object) {
         super(object);
         if (object != null) {
             this.name        = object.getName();
             this.description = object.getDescription();
-            this.operations  = copyList(object.getOperations(), OperationMetadata.class);
+            this.operations  = copyList(object.getOperations(), DefaultOperationMetadata.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultOperationChainMetadata}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultOperationChainMetadata} instance is created using the
-     *       {@linkplain #DefaultOperationChainMetadata(OperationChainMetadata) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultOperationChainMetadata castOrCopy(final OperationChainMetadata object) {
-        if (object == null || object instanceof DefaultOperationChainMetadata) {
-            return (DefaultOperationChainMetadata) object;
-        }
-        return new DefaultOperationChainMetadata(object);
-    }
-
-    /**
      * Returns the name as used by the service for this chain.
      *
      * @return name as used by the service for this chain.
      */
-    @Override
     @XmlElement(name = "name", namespace = Namespaces.SRV, required = true)
+    @UML(identifier="name", obligation=MANDATORY, specification=ISO_19115)
     public InternationalString getName() {
         return name;
     }
@@ -168,8 +154,8 @@
      *
      * @return narrative explanation of the services in the chain and resulting output, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "description", namespace = Namespaces.SRV)
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
@@ -187,20 +173,30 @@
     /**
      * Returns information about the operations applied by the chain.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the operations applied by the chain.
      */
-    @Override
     @XmlElement(name = "operation", namespace = Namespaces.SRV, required = true)
-    public List<OperationMetadata> getOperations() {
-        return operations = nonNullList(operations, OperationMetadata.class);
+    @UML(identifier="operation", obligation=MANDATORY, specification=ISO_19115)
+    public List<DefaultOperationMetadata> getOperations() {
+        return operations = nonNullList(operations, DefaultOperationMetadata.class);
     }
 
     /**
      * Sets the information about the operations applied by the chain.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information about the operations applied by the chain.
      */
-    public void setOperations(final List<? extends OperationMetadata> newValues) {
-        operations = writeList(newValues, operations, OperationMetadata.class);
+    public void setOperations(final List<? extends DefaultOperationMetadata> newValues) {
+        operations = writeList(newValues, operations, DefaultOperationMetadata.class);
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
index 36b7fc1..e099dbf 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
@@ -21,15 +21,22 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.InternationalString;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
-import org.opengis.metadata.identification.OperationMetadata;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import org.apache.sis.internal.jaxb.code.DCPList;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Parameter information.
@@ -41,6 +48,14 @@
  * {@code   └─connectPoint………………………………………………} Handle for accessing the service interface.
  * {@code       └─linkage…………………………………………………} Location for on-line access using a URL address or similar addressing scheme.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code OperationMetadata} interface.
+ * </div>
+ *
  * <h2>Limitations</h2>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -68,11 +83,12 @@
     "dependsOn"
 })
 @XmlRootElement(name = "SV_OperationMetadata", namespace = Namespaces.SRV)
-public class DefaultOperationMetadata extends ISOMetadata implements OperationMetadata {
+@UML(identifier="SV_OperationMetadata", specification=ISO_19115)
+public class DefaultOperationMetadata extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
-    private static final long serialVersionUID = -6120853428175790473L;
+    private static final long serialVersionUID = -3513177609655567627L;
 
     /**
      * An unique identifier for this interface.
@@ -82,7 +98,7 @@
     /**
      * Distributed computing platforms on which the operation has been implemented.
      */
-    private Collection<DistributedComputingPlatform> distributedComputingPlatforms;
+    private Collection<CodeList<?>> distributedComputingPlatforms;
 
     /**
      * Free text description of the intent of the operation and the results of the operation.
@@ -107,7 +123,7 @@
     /**
      * List of operation that must be completed immediately.
      */
-    private List<OperationMetadata> dependsOn;
+    private List<DefaultOperationMetadata> dependsOn;
 
     /**
      * Constructs an initially empty operation metadata.
@@ -116,76 +132,33 @@
     }
 
     /**
-     * Constructs a new operation metadata initialized to the specified values.
-     *
-     * @param operationName  an unique identifier for this interface.
-     * @param platform       distributed computing platforms on which the operation has been implemented.
-     * @param connectPoint   handle for accessing the service interface.
-     */
-    public DefaultOperationMetadata(final String operationName,
-                                    final DistributedComputingPlatform platform,
-                                    final OnlineResource connectPoint)
-    {
-        this.operationName                 = operationName;
-        this.distributedComputingPlatforms = singleton(platform, DistributedComputingPlatform.class);
-        this.connectPoints                 = singleton(connectPoint, OnlineResource.class);
-    }
-
-    /**
      * Constructs a new instance initialized with the values from the specified metadata object.
      * This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(OperationMetadata)
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public DefaultOperationMetadata(final OperationMetadata object) {
+    public DefaultOperationMetadata(final DefaultOperationMetadata object) {
         super(object);
         if (object != null) {
             this.operationName                 = object.getOperationName();
-            this.distributedComputingPlatforms = copyCollection(object.getDistributedComputingPlatforms(), DistributedComputingPlatform.class);
+            this.distributedComputingPlatforms = copyCollection(object.getDistributedComputingPlatforms(), (Class) CodeList.class);
             this.operationDescription          = object.getOperationDescription();
             this.invocationName                = object.getInvocationName();
             this.connectPoints                 = copyCollection(object.getConnectPoints(), OnlineResource.class);
             this.parameters                    = copySet(object.getParameters(), (Class) ParameterDescriptor.class);
-            this.dependsOn                     = copyList(object.getDependsOn(), OperationMetadata.class);
+            this.dependsOn                     = copyList(object.getDependsOn(), DefaultOperationMetadata.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultOperationMetadata}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultOperationMetadata} instance is created using the
-     *       {@linkplain #DefaultOperationMetadata(OperationMetadata) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultOperationMetadata castOrCopy(final OperationMetadata object) {
-        if (object == null || object instanceof DefaultOperationMetadata) {
-            return (DefaultOperationMetadata) object;
-        }
-        return new DefaultOperationMetadata(object);
-    }
-
-    /**
      * Returns an unique identifier for this interface.
      *
      * @return an unique identifier for this interface.
      */
-    @Override
     @XmlElement(name = "operationName", required = true)
+    @UML(identifier="operationName", obligation=MANDATORY, specification=ISO_19115)
     public String getOperationName() {
         return operationName;
     }
@@ -203,21 +176,56 @@
     /**
      * Returns the distributed computing platforms (DCPs) on which the operation has been implemented.
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The element type will be changed to the {@code DistributedComputingPlatform} code list
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return distributed computing platforms on which the operation has been implemented.
      */
-    @Override
+    @XmlJavaTypeAdapter(DCPList.class)
     @XmlElement(name = "distributedComputingPlatform", required = true)
-    public Collection<DistributedComputingPlatform> getDistributedComputingPlatforms() {
-        return distributedComputingPlatforms = nonNullCollection(distributedComputingPlatforms, DistributedComputingPlatform.class);
+    @UML(identifier="distributedComputingPlatform", obligation=MANDATORY, specification=ISO_19115)
+    public Collection<CodeList<?>> getDistributedComputingPlatforms() {
+        return distributedComputingPlatforms = nonNullCollection(distributedComputingPlatforms, (Class) CodeList.class);
     }
 
     /**
      * Sets the distributed computing platforms on which the operation has been implemented.
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The element type will be changed to the {@code DistributedComputingPlatform} code list when GeoAPI will provide
+     * it (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@preformat java
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     * }
+     * </div>
+     *
      * @param  newValues  the new distributed computing platforms on which the operation has been implemented.
      */
-    public void setDistributedComputingPlatforms(final Collection<? extends DistributedComputingPlatform> newValues) {
-        distributedComputingPlatforms = writeCollection(newValues, distributedComputingPlatforms, DistributedComputingPlatform.class);
+    public void setDistributedComputingPlatforms(final Collection<? extends CodeList<?>> newValues) {
+        distributedComputingPlatforms = writeCollection(newValues, distributedComputingPlatforms, (Class) CodeList.class);
     }
 
     /**
@@ -225,8 +233,8 @@
      *
      * @return free text description of the intent of the operation and the results of the operation, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "operationDescription")
+    @UML(identifier="operationDescription", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getOperationDescription() {
         return operationDescription;
     }
@@ -247,8 +255,8 @@
      * @return the name used to invoke this interface within the context of the distributed computing platforms,
      *         or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "invocationName")
+    @UML(identifier="invocationName", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getInvocationName() {
         return invocationName;
     }
@@ -268,8 +276,8 @@
      *
      * @return handle for accessing the service interface.
      */
-    @Override
     @XmlElement(name = "connectPoint", required = true)
+    @UML(identifier="connectPoint", obligation=MANDATORY, specification=ISO_19115)
     public Collection<OnlineResource> getConnectPoints() {
         return connectPoints = nonNullCollection(connectPoints, OnlineResource.class);
     }
@@ -288,9 +296,9 @@
      *
      * @return the parameters that are required for this interface, or an empty collection if none.
      */
-    @Override
     @XmlElement(name = "parameter")
     @SuppressWarnings({"unchecked", "rawtypes"})
+    @UML(identifier="parameters", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<ParameterDescriptor<?>> getParameters() {
         return parameters = nonNullCollection(parameters, (Class) ParameterDescriptor.class);
     }
@@ -308,20 +316,30 @@
     /**
      * Returns the list of operation that must be completed immediately before current operation is invoked.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return list of operation that must be completed immediately, or an empty list if none.
      */
-    @Override
     @XmlElement(name = "dependsOn")
-    public List<OperationMetadata> getDependsOn() {
-        return dependsOn = nonNullList(dependsOn, OperationMetadata.class);
+    @UML(identifier="dependsOn", obligation=OPTIONAL, specification=ISO_19115)
+    public List<DefaultOperationMetadata> getDependsOn() {
+        return dependsOn = nonNullList(dependsOn, DefaultOperationMetadata.class);
     }
 
     /**
      * Sets the list of operation that must be completed before current operation is invoked.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new list of operation.
      */
-    public void setDependsOn(final List<? extends OperationMetadata> newValues) {
-        dependsOn = writeList(newValues, dependsOn, OperationMetadata.class);
+    public void setDependsOn(final List<? extends DefaultOperationMetadata> newValues) {
+        dependsOn = writeList(newValues, dependsOn, DefaultOperationMetadata.class);
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java
index 64e55b0..f128582 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java
@@ -33,6 +33,11 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Level of detail expressed as a scale factor or a ground distance.
@@ -168,13 +173,13 @@
         super(object);
         if (object != null) {
             for (byte p=SCALE; p<=TEXT; p++) {
-                final Object c;
+                Object c = null;
                 switch (p) {
                     case SCALE:    c = object.getEquivalentScale(); break;
-                    case DISTANCE: c = object.getDistance();        break;
-                    case VERTICAL: c = object.getVertical();        break;
-                    case ANGULAR:  c = object.getAngularDistance(); break;
-                    case TEXT:     c = object.getLevelOfDetail();   break;
+                    case DISTANCE: c = object.getDistance(); break;
+                    case VERTICAL: if (c instanceof DefaultResolution) c = ((DefaultResolution) object).getVertical(); break;
+                    case ANGULAR:  if (c instanceof DefaultResolution) c = ((DefaultResolution) object).getAngularDistance(); break;
+                    case TEXT:     if (c instanceof DefaultResolution) c = ((DefaultResolution) object).getLevelOfDetail(); break;
                     default:       throw new AssertionError(p);
                 }
                 if (c != null) {
@@ -291,9 +296,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "vertical")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="vertical", obligation=CONDITIONAL, specification=ISO_19115)
     @ValueRange(minimum=0, isMinIncluded=false)
     public Double getVertical() {
         return (property == VERTICAL) ? (Double) value : null;
@@ -324,9 +329,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "angularDistance")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="angularDistance", obligation=CONDITIONAL, specification=ISO_19115)
     @ValueRange(minimum=0, isMinIncluded=false)
     public Double getAngularDistance() {
         return (property == ANGULAR) ? (Double) value : null;
@@ -357,9 +362,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "levelOfDetail")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="levelOfDetail", obligation=CONDITIONAL, specification=ISO_19115)
     public InternationalString getLevelOfDetail() {
         return (property == TEXT) ? (InternationalString) value : null;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
index 288cc98..ee128ae 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
@@ -21,18 +21,24 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.opengis.util.CodeList;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.identification.DataIdentification;
 import org.opengis.metadata.distribution.StandardOrderProcess;
 import org.opengis.metadata.identification.ServiceIdentification;
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.CouplingType;
-import org.opengis.metadata.identification.OperationChainMetadata;
-import org.opengis.metadata.identification.OperationMetadata;
+import org.apache.sis.internal.jaxb.code.SV_CouplingType;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Identification of capabilities which a service provider makes available to a service user
@@ -111,12 +117,12 @@
     /**
      * Type of coupling between service and associated data (if exist).
      */
-    private CouplingType couplingType;
+    private CodeList<?> couplingType;
 
     /**
      * Further description of the data coupling in the case of tightly coupled services.
      */
-    private Collection<CoupledResource> coupledResources;
+    private Collection<DefaultCoupledResource> coupledResources;
 
     /**
      * References to the resource on which the service operates.
@@ -136,7 +142,7 @@
     /**
      * Information about the operations that comprise the service.
      */
-    private Collection<OperationMetadata> containsOperations;
+    private Collection<DefaultOperationMetadata> containsOperations;
 
     /**
      * Information on the resources that the service operates on.
@@ -146,7 +152,7 @@
     /**
      * Information about the chain applied by the service.
      */
-    private Collection<OperationChainMetadata> containsChain;
+    private Collection<DefaultOperationChainMetadata> containsChain;
 
     /**
      * Constructs an initially empty service identification.
@@ -180,18 +186,19 @@
      */
     public DefaultServiceIdentification(final ServiceIdentification object) {
         super(object);
-        if (object != null) {
-            serviceType         = object.getServiceType();
-            serviceTypeVersions = copyCollection(object.getServiceTypeVersions(), String.class);
-            accessProperties    = object.getAccessProperties();
-            couplingType        = object.getCouplingType();
-            coupledResources    = copyCollection(object.getCoupledResources(), CoupledResource.class);
-            operatedDatasets    = copyCollection(object.getOperatedDatasets(), Citation.class);
-            profiles            = copyCollection(object.getProfiles(), Citation.class);
-            serviceStandards    = copyCollection(object.getServiceStandards(), Citation.class);
-            containsOperations  = copyCollection(object.getContainsOperations(), OperationMetadata.class);
-            operatesOn          = copyCollection(object.getOperatesOn(), DataIdentification.class);
-            containsChain       = copyCollection(object.getContainsChain(), OperationChainMetadata.class);
+        if (object instanceof DefaultServiceIdentification) {
+            final DefaultServiceIdentification c = (DefaultServiceIdentification) object;
+            serviceType         = c.getServiceType();
+            serviceTypeVersions = copyCollection(c.getServiceTypeVersions(), String.class);
+            accessProperties    = c.getAccessProperties();
+            couplingType        = c.getCouplingType();
+            coupledResources    = copyCollection(c.getCoupledResources(), DefaultCoupledResource.class);
+            operatedDatasets    = copyCollection(c.getOperatedDatasets(), Citation.class);
+            profiles            = copyCollection(c.getProfiles(), Citation.class);
+            serviceStandards    = copyCollection(c.getServiceStandards(), Citation.class);
+            containsOperations  = copyCollection(c.getContainsOperations(), DefaultOperationMetadata.class);
+            operatesOn          = copyCollection(c.getOperatesOn(), DataIdentification.class);
+            containsChain       = copyCollection(c.getContainsChain(), DefaultOperationChainMetadata.class);
         }
     }
 
@@ -227,8 +234,8 @@
      *
      * @return a service type name.
      */
-    @Override
     @XmlElement(name = "serviceType", required = true)
+    @UML(identifier="serviceType", obligation=MANDATORY, specification=ISO_19115)
     public GenericName getServiceType() {
         return serviceType;
     }
@@ -248,8 +255,8 @@
      *
      * @return the versions of the service.
      */
-    @Override
     @XmlElement(name = "serviceTypeVersion")
+    @UML(identifier="serviceTypeVersion", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<String> getServiceTypeVersions() {
         return serviceTypeVersions = nonNullCollection(serviceTypeVersions, String.class);
     }
@@ -270,8 +277,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "accessProperties")
+    @UML(identifier="accessProperties", obligation=OPTIONAL, specification=ISO_19115)
     public StandardOrderProcess getAccessProperties() {
         return accessProperties;
 
@@ -292,20 +299,55 @@
     /**
      * Returns type of coupling between service and associated data (if exist).
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The return type will be changed to the {@code CouplingType} code list
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return type of coupling between service and associated data, or {@code null} if none.
      */
-    @Override
+    @XmlJavaTypeAdapter(SV_CouplingType.class)
     @XmlElement(name = "couplingType")
-    public CouplingType getCouplingType() {
+    @UML(identifier="couplingType", obligation=CONDITIONAL, specification=ISO_19115)
+    public CodeList<?> getCouplingType() {
         return couplingType;
     }
 
     /**
      * Sets the type of coupling between service and associated data.
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The argument type will be changed to the {@code CouplingType} code list when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@preformat java
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     * }
+     * </div>
+     *
      * @param  newValue  the new type of coupling between service and associated data.
      */
-    public void setCouplingType(final CouplingType newValue) {
+    public void setCouplingType(final CodeList<?> newValue) {
         checkWritePermission(couplingType);
         couplingType = newValue;
     }
@@ -313,21 +355,31 @@
     /**
      * Returns further description(s) of the data coupling in the case of tightly coupled services.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code CoupledResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return further description(s) of the data coupling in the case of tightly coupled services.
      */
-    @Override
     @XmlElement(name = "coupledResource")
-    public Collection<CoupledResource> getCoupledResources() {
-        return coupledResources = nonNullCollection(coupledResources, CoupledResource.class);
+    @UML(identifier="coupledResource", obligation=CONDITIONAL, specification=ISO_19115)
+    public Collection<DefaultCoupledResource> getCoupledResources() {
+        return coupledResources = nonNullCollection(coupledResources, DefaultCoupledResource.class);
     }
 
     /**
      * Sets further description(s) of the data coupling in the case of tightly coupled services.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code CoupledResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new further description(s) of the data coupling.
      */
-    public void setCoupledResources(final Collection<? extends CoupledResource> newValues) {
-        coupledResources = writeCollection(newValues, coupledResources, CoupledResource.class);
+    public void setCoupledResources(final Collection<? extends DefaultCoupledResource> newValues) {
+        coupledResources = writeCollection(newValues, coupledResources, DefaultCoupledResource.class);
     }
 
     /**
@@ -337,8 +389,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="operatedDataset", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getOperatedDatasets() {
         return operatedDatasets = nonNullCollection(operatedDatasets, Citation.class);
     }
@@ -361,8 +413,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="profile", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getProfiles() {
         return profiles = nonNullCollection(profiles, Citation.class);
     }
@@ -383,8 +435,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="serviceStandard", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getServiceStandards() {
         return serviceStandards = nonNullCollection(serviceStandards, Citation.class);
     }
@@ -403,21 +455,31 @@
     /**
      * Provides information about the operations that comprise the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the operations that comprise the service.
      */
-    @Override
     @XmlElement(name = "containsOperations")
-    public Collection<OperationMetadata> getContainsOperations() {
-        return containsOperations = nonNullCollection(containsOperations, OperationMetadata.class);
+    @UML(identifier="containsOperations", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultOperationMetadata> getContainsOperations() {
+        return containsOperations = nonNullCollection(containsOperations, DefaultOperationMetadata.class);
     }
 
     /**
      * Sets information(s) about the operations that comprise the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information(s) about the operations that comprise the service.
      */
-    public void setContainsOperations(final Collection<? extends OperationMetadata> newValues) {
-        containsOperations = writeCollection(newValues, containsOperations, OperationMetadata.class);
+    public void setContainsOperations(final Collection<? extends DefaultOperationMetadata> newValues) {
+        containsOperations = writeCollection(newValues, containsOperations, DefaultOperationMetadata.class);
     }
 
     /**
@@ -425,8 +487,8 @@
      *
      * @return information on the resources that the service operates on.
      */
-    @Override
     @XmlElement(name = "operatesOn")
+    @UML(identifier="operatesOn", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<DataIdentification> getOperatesOn() {
         return operatesOn = nonNullCollection(operatesOn, DataIdentification.class);
     }
@@ -443,25 +505,35 @@
     /**
      * Provides information about the chain applied by the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationChainMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the chain applied by the service.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<OperationChainMetadata> getContainsChain() {
-        return containsChain = nonNullCollection(containsChain, OperationChainMetadata.class);
+    @UML(identifier="containsChain", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultOperationChainMetadata> getContainsChain() {
+        return containsChain = nonNullCollection(containsChain, DefaultOperationChainMetadata.class);
     }
 
     /**
      * Sets the information about the chain applied by the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationChainMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information about the chain applied by the service.
      *
      * @since 0.5
      */
-    public void setContainsChain(final Collection<? extends OperationChainMetadata>  newValues) {
-        containsChain = writeCollection(newValues, containsChain, OperationChainMetadata.class);
+    public void setContainsChain(final Collection<? extends DefaultOperationChainMetadata>  newValues) {
+        containsChain = writeCollection(newValues, containsChain, DefaultOperationChainMetadata.class);
     }
 
 
@@ -499,7 +571,7 @@
     }
 
     @XmlElement(name = "containsChain")
-    private Collection<OperationChainMetadata> getOperationChain() {
+    private Collection<DefaultOperationChainMetadata> getOperationChain() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getContainsChain() : null;
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java
index eadaf03..513d1bf 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java
@@ -30,6 +30,10 @@
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toDate;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toMilliseconds;
 
@@ -155,9 +159,12 @@
             usageDate                 = toMilliseconds(object.getUsageDate());
             userDeterminedLimitations = object.getUserDeterminedLimitations();
             userContactInfo           = copyCollection(object.getUserContactInfo(), ResponsibleParty.class);
-            responses                 = copyCollection(object.getResponses(), InternationalString.class);
-            additionalDocumentation   = copyCollection(object.getAdditionalDocumentation(), Citation.class);
-            identifiedIssues          = copyCollection(object.getIdentifiedIssues(), Citation.class);
+            if (object instanceof DefaultUsage) {
+                final DefaultUsage c = (DefaultUsage) object;
+                responses                 = copyCollection(c.getResponses(), InternationalString.class);
+                additionalDocumentation   = copyCollection(c.getAdditionalDocumentation(), Citation.class);
+                identifiedIssues          = copyCollection(c.getIdentifiedIssues(), Citation.class);
+            }
         }
     }
 
@@ -286,8 +293,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="response", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<InternationalString> getResponses() {
         return responses = nonNullCollection(responses, InternationalString.class);
     }
@@ -310,8 +317,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="additionalDocumentation", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAdditionalDocumentation() {
         return additionalDocumentation = nonNullCollection(additionalDocumentation, Citation.class);
     }
@@ -335,8 +342,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="identifiedIssues", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getIdentifiedIssues() {
         return identifiedIssues = nonNullCollection(identifiedIssues, Citation.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/OperationName.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/OperationName.java
index a7e7356..0bcb09f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/OperationName.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/OperationName.java
@@ -19,17 +19,11 @@
 import java.util.Map;
 import java.util.HashMap;
 import java.util.Collection;
-import java.util.Collections;
-import java.io.Serializable;
-import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
-import org.opengis.metadata.identification.OperationMetadata;
 import org.apache.sis.internal.util.Strings;
 
 
 /**
- * An {@code OperationMetadata} placeholder to be replaced later by a reference to an other {@link OperationMetadata}.
+ * An {@code OperationMetadata} placeholder to be replaced later by a reference to an other {@code OperationMetadata}.
  * This temporary place holder is used when the operation name is unmarshalled before the actual operation definition.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -37,37 +31,25 @@
  * @since   0.5
  * @module
  */
-final class OperationName implements OperationMetadata, Serializable {
+final class OperationName extends DefaultOperationMetadata {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = -7958898214063034276L;
-
-    /**
-     * The operation name.
-     */
-    private final String operationName;
+    private static final long serialVersionUID = 7221543581387125873L;
 
     /**
      * Creates a new placeholder for the operation of the given name.
      */
     OperationName(final String operationName) {
-        this.operationName = operationName;
+        setOperationName(operationName);
     }
 
     /**
-     * Returns the operation name.
-     */
-    @Override public String                                   getOperationName()                 {return operationName;}
-    @Override public Collection<DistributedComputingPlatform> getDistributedComputingPlatforms() {return Collections.emptySet();}
-    @Override public Collection<OnlineResource>               getConnectPoints()                 {return Collections.emptySet();}
-
-    /**
      * Returns a string representation of this placeholder.
      */
     @Override
     public String toString() {
-        return Strings.bracket("OperationMetadata", operationName);
+        return Strings.bracket("OperationMetadata", getOperationName());
     }
 
     /**
@@ -80,25 +62,23 @@
      * <p>This method is invoked at unmarshalling time for resolving the {@code OperationMetadata} instance which
      * were identified only by a name in a {@code <srv:operationName>} element.</p>
      */
-    static void resolve(final Collection<OperationMetadata> containsOperations, final Collection<CoupledResource> coupledResources) {
-        final Map<String,OperationMetadata> byName = new HashMap<>();
-        for (final OperationMetadata operation : containsOperations) {
+    static void resolve(final Collection<DefaultOperationMetadata> containsOperations, final Collection<DefaultCoupledResource> coupledResources) {
+        final Map<String,DefaultOperationMetadata> byName = new HashMap<>();
+        for (final DefaultOperationMetadata operation : containsOperations) {
             add(byName, operation.getOperationName(), operation);
         }
-        for (final CoupledResource resource : coupledResources) {
-            if (resource instanceof DefaultCoupledResource) {
-                OperationMetadata operation = resource.getOperation();
-                if (operation instanceof OperationName) {
-                    final String name = operation.getOperationName();
+        for (final DefaultCoupledResource resource : coupledResources) {
+            DefaultOperationMetadata operation = resource.getOperation();
+            if (operation instanceof OperationName) {
+                final String name = operation.getOperationName();
+                operation = byName.get(name);
+                if (operation == null) {
                     operation = byName.get(name);
                     if (operation == null) {
-                        operation = byName.get(name);
-                        if (operation == null) {
-                            continue;
-                        }
+                        continue;
                     }
-                    ((DefaultCoupledResource) resource).setOperation(operation);
                 }
+                resource.setOperation(operation);
             }
         }
     }
@@ -107,9 +87,9 @@
      * Adds the given operation in the given map under the given name. If an entry already exists for the given name,
      * then this method sets the value to {@code null} for meaning that we have duplicated values for that name.
      */
-    private static void add(final Map<String,OperationMetadata> byName, final String name, final OperationMetadata operation) {
+    private static void add(final Map<String,DefaultOperationMetadata> byName, final String name, final DefaultOperationMetadata operation) {
         final boolean exists = byName.containsKey(name);
-        final OperationMetadata previous = byName.put(name, operation);
+        final DefaultOperationMetadata previous = byName.put(name, operation);
         if (previous != operation && (previous != null || exists)) {
             byName.put(name, null);                                         // Mark the entry as duplicated.
         }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java
index 3cac3b8..a527522 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java
@@ -47,8 +47,8 @@
  * {@code  ├─} {@linkplain org.opengis.metadata.identification.AssociationType Association type}<br>
  * {@code  ├─} {@linkplain org.opengis.metadata.identification.InitiativeType  Initiative type}<br>
  * {@code  ├─} {@linkplain org.opengis.metadata.identification.TopicCategory   Topic category}<br>
- * {@code  ├─} {@linkplain org.opengis.metadata.identification.CouplingType    Coupling type}<br>
- * {@code  └─} {@linkplain org.opengis.metadata.identification.DistributedComputingPlatform Distributed computing platform}<br>
+ * {@code  ├─} Coupling type}<br>
+ * {@code  └─} Distributed computing platform}<br>
  * </td><td class="sep" style="width: 50%; white-space: nowrap">
  *                 {@linkplain org.apache.sis.metadata.iso.identification.AbstractIdentification        Identification} «abstract»<br>
  * {@code  ├─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultResolution             Resolution}<br>
@@ -63,12 +63,12 @@
  * {@code      └─} {@linkplain org.opengis.metadata.identification.InitiativeType                       Initiative type} «code list»<br>
  *                 {@linkplain org.apache.sis.metadata.iso.identification.DefaultDataIdentification     Data identification}<br>
  *                 {@linkplain org.apache.sis.metadata.iso.identification.DefaultServiceIdentification  Service identification}<br>
- * {@code  ├─}     {@linkplain org.opengis.metadata.identification.CouplingType                         Coupling type} «code list»<br>
+ * {@code  ├─}     Coupling type} «code list»<br>
  * {@code  ├─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultCoupledResource        Coupled resource}<br>
  * {@code  ├─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultOperationMetadata      Operation metadata}<br>
- * {@code  │   ├─} {@linkplain org.opengis.metadata.identification.DistributedComputingPlatform         Distributed computing platform} «code list»<br>
+ * {@code  │   ├─} Distributed computing platform} «code list»<br>
  * {@code  │   └─} {@linkplain org.apache.sis.parameter.DefaultParameterDescriptor                      Parameter descriptor}<br>
- * {@code  │       └─} {@linkplain org.opengis.parameter.ParameterDirection                             Parameter direction} «enum»<br>
+ * {@code  │       └─} Parameter direction} «enum»<br>
  * {@code  └─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultOperationChainMetadata Operation chain metadata}<br>
  * </td></tr></table>
  *
@@ -116,12 +116,10 @@
     @XmlJavaTypeAdapter(CI_Citation.class),
     @XmlJavaTypeAdapter(CI_OnlineResource.class),
     @XmlJavaTypeAdapter(CI_ResponsibleParty.class),
-    @XmlJavaTypeAdapter(DCPList.class),
     @XmlJavaTypeAdapter(EX_Extent.class),
     @XmlJavaTypeAdapter(GO_DateTime.class),
     @XmlJavaTypeAdapter(GO_GenericName.class),
     @XmlJavaTypeAdapter(MD_AggregateInformation.class),
-    @XmlJavaTypeAdapter(MD_AssociatedResource.class),
     @XmlJavaTypeAdapter(MD_BrowseGraphic.class),
     @XmlJavaTypeAdapter(MD_CharacterSetCode.class),
     @XmlJavaTypeAdapter(MD_Constraints.class),
@@ -139,7 +137,6 @@
     @XmlJavaTypeAdapter(MD_TopicCategoryCode.class),
     @XmlJavaTypeAdapter(MD_Usage.class),
     @XmlJavaTypeAdapter(SV_CoupledResource.class),
-    @XmlJavaTypeAdapter(SV_CouplingType.class),
     @XmlJavaTypeAdapter(SV_OperationMetadata.class),
     @XmlJavaTypeAdapter(SV_OperationChainMetadata.class),
     @XmlJavaTypeAdapter(SV_Parameter.class),
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultLineage.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultLineage.java
index 490471e..daf9d47 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultLineage.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultLineage.java
@@ -23,7 +23,7 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.lineage.Source;
 import org.opengis.metadata.lineage.Lineage;
 import org.opengis.metadata.lineage.ProcessStep;
@@ -33,6 +33,11 @@
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.metadata.MD_Scope;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the events or source data used in constructing the data specified by
@@ -138,10 +143,12 @@
         super(object);
         if (object != null) {
             statement               = object.getStatement();
-            scope                   = object.getScope();
-            additionalDocumentation = copyCollection(object.getAdditionalDocumentation(), Citation.class);
             processSteps            = copyCollection(object.getProcessSteps(), ProcessStep.class);
             sources                 = copyCollection(object.getSources(), Source.class);
+            if (object instanceof DefaultLineage) {
+                scope                   = ((DefaultLineage) object).getScope();
+                additionalDocumentation = copyCollection(((DefaultLineage) object).getAdditionalDocumentation(), Citation.class);
+            }
         }
     }
 
@@ -200,9 +207,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "scope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="scope", obligation=OPTIONAL, specification=ISO_19115)
     public Scope getScope() {
         return scope;
     }
@@ -226,8 +233,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="additionalDocumentation", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAdditionalDocumentation() {
         return additionalDocumentation = nonNullCollection(additionalDocumentation, Citation.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
index 87bfc9a..0d4ee9c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
@@ -27,7 +27,7 @@
 import org.opengis.temporal.TemporalPrimitive;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.ResponsibleParty;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.lineage.Source;
 import org.opengis.metadata.lineage.Processing;
 import org.opengis.metadata.lineage.ProcessStep;
@@ -41,6 +41,11 @@
 import org.apache.sis.internal.jaxb.metadata.MD_Scope;
 import org.apache.sis.internal.util.TemporalUtilities;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about an event or transformation in the life of a resource.
@@ -174,12 +179,14 @@
             rationale             = object.getRationale();
             stepDateTime          = TemporalUtilities.createInstant(object.getDate());
             processors            = copyCollection(object.getProcessors(), ResponsibleParty.class);
-            references            = copyCollection(object.getReferences(), Citation.class);
             sources               = copyCollection(object.getSources(), Source.class);
-            scope                 = object.getScope();
             outputs               = copyCollection(object.getOutputs(), Source.class);
             processingInformation = object.getProcessingInformation();
             reports               = copyCollection(object.getReports(), ProcessStepReport.class);
+            if (object instanceof DefaultProcessStep) {
+                references = copyCollection(((DefaultProcessStep) object).getReferences(), Citation.class);
+                scope      = ((DefaultProcessStep) object).getScope();
+            }
         }
     }
 
@@ -340,8 +347,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="reference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getReferences() {
         return references = nonNullCollection(references, Citation.class);
     }
@@ -364,9 +371,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "scope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="scope", obligation=OPTIONAL, specification=ISO_19115)
     public Scope getScope() {
         return scope;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
index 092242f..0c13ea5 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
@@ -32,7 +32,7 @@
 import org.opengis.metadata.lineage.ProcessStep;
 import org.opengis.metadata.identification.Resolution;
 import org.opengis.metadata.identification.RepresentativeFraction;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.referencing.ReferenceSystem;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.metadata.iso.ISOMetadata;
@@ -46,6 +46,12 @@
 import org.apache.sis.internal.metadata.Dependencies;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the source data used in creating the data specified by the scope.
@@ -180,14 +186,19 @@
         super(object);
         if (object != null) {
             description             = object.getDescription();
-            sourceSpatialResolution = object.getSourceSpatialResolution();
             sourceReferenceSystem   = object.getSourceReferenceSystem();
             sourceCitation          = object.getSourceCitation();
-            sourceMetadata          = copyCollection(object.getSourceMetadata(), Citation.class);
-            scope                   = object.getScope();
             sourceSteps             = copyCollection(object.getSourceSteps(), ProcessStep.class);
             processedLevel          = object.getProcessedLevel();
             resolution              = object.getResolution();
+            if (object instanceof DefaultSource) {
+                sourceSpatialResolution = ((DefaultSource) object).getSourceSpatialResolution();
+                sourceMetadata          = copyCollection(((DefaultSource) object).getSourceMetadata(), Citation.class);
+                scope                   = ((DefaultSource) object).getScope();
+            } else {
+                setScaleDenominator(object.getScaleDenominator());
+                setSourceExtents(object.getSourceExtents());
+            }
         }
     }
 
@@ -244,9 +255,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "sourceSpatialResolution")
     @XmlJavaTypeAdapter(MD_Resolution.Since2014.class)
+    @UML(identifier="sourceSpatialResolution", obligation=OPTIONAL, specification=ISO_19115)
     public Resolution getSourceSpatialResolution() {
         return sourceSpatialResolution;
     }
@@ -366,8 +377,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="sourceMetadata", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getSourceMetadata() {
         return sourceMetadata = nonNullCollection(sourceMetadata, Citation.class);
     }
@@ -391,9 +402,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "scope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="scope", obligation=CONDITIONAL, specification=ISO_19115)
     public Scope getScope() {
         return scope;
     }
@@ -431,7 +442,7 @@
                         scope = new DefaultScope(scope);
                         this.scope = scope;
                     } else {
-                        return Collections.unmodifiableCollection(scope.getExtents());
+                        return Collections.singleton(scope.getExtent());
                     }
                 }
                 return ((DefaultScope) scope).getExtents();
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
index 6d33e52..2e3f0ba 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
@@ -30,7 +30,7 @@
 import org.opengis.metadata.maintenance.MaintenanceInformation;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.maintenance.ScopeDescription;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.temporal.PeriodDuration;
 import org.opengis.util.InternationalString;
 import org.apache.sis.metadata.iso.ISOMetadata;
@@ -41,6 +41,10 @@
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.util.CollectionsExt;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
 
 
@@ -89,6 +93,11 @@
     private static final long serialVersionUID = -8736825706141936429L;
 
     /**
+     * New code list item defined in ISO 19115:2014.
+     */
+    private static final DateType NEXT_UPDATE = DateType.valueOf("NEXT_UPDATE");
+
+    /**
      * Frequency with which changes and additions are made to the resource after the
      * initial resource is completed.
      */
@@ -149,11 +158,18 @@
         super(object);
         if (object != null) {
             maintenanceAndUpdateFrequency   = object.getMaintenanceAndUpdateFrequency();
-            maintenanceDates                = copyCollection(object.getMaintenanceDates(), CitationDate.class);
             userDefinedMaintenanceFrequency = object.getUserDefinedMaintenanceFrequency();
-            maintenanceScopes               = copyCollection(object.getMaintenanceScopes(), Scope.class);
             maintenanceNotes                = copyCollection(object.getMaintenanceNotes(), InternationalString.class);
-            contacts                        = copyCollection(object.getContacts(), ResponsibleParty.class);
+            if (object instanceof DefaultMaintenanceInformation) {
+                final DefaultMaintenanceInformation c = (DefaultMaintenanceInformation) object;
+                maintenanceDates                = copyCollection(c.getMaintenanceDates(), CitationDate.class);
+                maintenanceScopes               = copyCollection(c.getMaintenanceScopes(), Scope.class);
+                contacts                        = copyCollection(c.getContacts(), ResponsibleParty.class);
+            } else {
+                setDateOfNextUpdate(object.getDateOfNextUpdate());
+                setUpdateScopes(object.getUpdateScopes());
+                setUpdateScopeDescriptions(object.getUpdateScopeDescriptions());
+            }
         }
     }
 
@@ -212,8 +228,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="maintenanceDate", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<CitationDate> getMaintenanceDates() {
         return maintenanceDates = nonNullCollection(maintenanceDates, CitationDate.class);
     }
@@ -236,8 +252,8 @@
      * @return scheduled revision date, or {@code null}.
      *
      * @deprecated As of ISO 19115:2014, replaced by {@link #getMaintenanceDates()} in order to enable inclusion
-     *             of a {@link DateType} to describe the type of the date. Note that {@link DateType#NEXT_UPDATE}
-     *             was added to that code list.
+     *             of a {@link DateType} to describe the type of the date. The associated date type is
+     *             {@code DateType.valueOf("NEXT_UPDATE")}.
      */
     @Override
     @Deprecated
@@ -246,9 +262,9 @@
     public Date getDateOfNextUpdate() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
             final Collection<CitationDate> dates = getMaintenanceDates();
-            if (dates != null) {                                                    // May be null on XML marshalling.
+            if (dates != null) {                                                // May be null on XML marshalling.
                 for (final CitationDate date : dates) {
-                    if (DateType.NEXT_UPDATE.equals(date.getDateType())) {
+                    if (NEXT_UPDATE.equals(date.getDateType())) {
                         return date.getDate();
                     }
                 }
@@ -271,7 +287,7 @@
             final Iterator<CitationDate> it = dates.iterator();
             while (it.hasNext()) {
                 final CitationDate date = it.next();
-                if (DateType.NEXT_UPDATE.equals(date.getDateType())) {
+                if (NEXT_UPDATE.equals(date.getDateType())) {
                     if (newValue == null) {
                         it.remove();
                         return;
@@ -283,7 +299,7 @@
             }
         }
         if (newValue != null) {
-            final CitationDate date = new DefaultCitationDate(newValue, DateType.NEXT_UPDATE);
+            final CitationDate date = new DefaultCitationDate(newValue, NEXT_UPDATE);
             if (dates != null) {
                 dates.add(date);
             } else {
@@ -321,8 +337,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="maintenanceScope", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Scope> getMaintenanceScopes() {
         return maintenanceScopes = nonNullCollection(maintenanceScopes, Scope.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScope.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScope.java
index 753740e..e4b5d43 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScope.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScope.java
@@ -21,12 +21,20 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.metadata.extent.Extent;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.maintenance.ScopeDescription;
+import org.apache.sis.internal.metadata.Dependencies;
+import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
+import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * The target resource and physical extent for which information is reported.
@@ -114,8 +122,12 @@
         super(object);
         if (object != null) {
             level            = object.getLevel();
-            extents          = copyCollection(object.getExtents(), Extent.class);
             levelDescription = copyCollection(object.getLevelDescription(), ScopeDescription.class);
+            if (object instanceof DefaultScope) {
+                extents = copyCollection(((DefaultScope) object).getExtents(), Extent.class);
+            } else {
+                extents = singleton(object.getExtent(), Extent.class);
+            }
         }
     }
 
@@ -172,8 +184,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "extent")
+    @UML(identifier="extent", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Extent> getExtents() {
         return extents = nonNullCollection(extents, Extent.class);
     }
@@ -190,6 +202,34 @@
     }
 
     /**
+     * Information about the spatial, vertical and temporal extent of the data specified by the scope.
+     * This method fetches the value from the {@linkplain #getExtents() extents} collection.
+     *
+     * @return Information about the extent of the data, or {@code null}.
+     *
+     * @deprecated As of ISO 19115:2014, replaced by {@link #getExtents()}.
+     */
+    @Override
+    @Deprecated
+    @Dependencies("getExtents")
+    public Extent getExtent() {
+        return LegacyPropertyAdapter.getSingleton(getExtents(), Extent.class, null, DefaultScope.class, "getExtent");
+    }
+
+    /**
+     * Sets information about the spatial, vertical and temporal extent of the data specified by the scope.
+     * This method stores the value in the {@linkplain #setExtents(Collection) extents} collection.
+     *
+     * @param newValue The new extent.
+     *
+     * @deprecated As of ISO 19115:2014, replaced by {@link #setExtents(Collection)}.
+     */
+    @Deprecated
+    public void setExtent(final Extent newValue) {
+        setExtents(CollectionsExt.singletonOrEmpty(newValue));
+    }
+
+    /**
      * Returns detailed descriptions about the level of the data specified by the scope.
      *
      * @return detailed description about the level of the data.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java
index 5bd306a..0091721 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java
@@ -41,8 +41,8 @@
  * {@code  ├─} {@linkplain org.apache.sis.metadata.iso.DefaultExtendedElementInformation   Extended element information}<br>
  * {@code  └─} {@linkplain org.apache.sis.metadata.iso.DefaultIdentifier                   Identifier}<br>
  * {@linkplain org.opengis.util.CodeList Code list}<br>
- * {@code  ├─} {@linkplain org.opengis.metadata.Datatype     Data type}<br>
- * {@code  └─} {@linkplain org.opengis.annotation.Obligation Obligation}<br>
+ * {@code  ├─} {@linkplain org.opengis.metadata.Datatype   Data type}<br>
+ * {@code  └─} {@linkplain org.opengis.metadata.Obligation Obligation}<br>
  * </td><td class="sep" style="width: 50%; white-space: nowrap">
  *                     {@linkplain org.apache.sis.metadata.iso.DefaultMetadata                     Metadata}<br>
  * {@code  ├─}         {@linkplain org.apache.sis.metadata.iso.DefaultMetadataScope                Metadata scope}<br>
@@ -51,7 +51,7 @@
  * {@code  ├─}         {@linkplain org.apache.sis.metadata.iso.DefaultMetadataExtensionInformation Metadata extension information}<br>
  * {@code  │   └─}     {@linkplain org.apache.sis.metadata.iso.DefaultExtendedElementInformation   Extended element information}<br>
  * {@code  │       ├─} {@linkplain org.opengis.metadata.Datatype                                   Data type} «code list»<br>
- * {@code  │       └─} {@linkplain org.opengis.annotation.Obligation                               Obligation} «code list»<br>
+ * {@code  │       └─} {@linkplain org.opengis.metadata.Obligation                                 Obligation} «code list»<br>
  * {@code  └─}         {@linkplain org.apache.sis.metadata.iso.DefaultIdentifier                   Identifier}<br>
  * </td></tr></table>
  *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
index 7709409..9cb894d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
@@ -26,6 +26,7 @@
 import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.apache.sis.metadata.iso.ISOMetadata;
+import org.apache.sis.metadata.iso.maintenance.DefaultScope;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultScope.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultScope.java
index e24976e..29916c9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultScope.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultScope.java
@@ -16,14 +16,9 @@
  */
 package org.apache.sis.metadata.iso.quality;
 
-import java.util.Collection;
 import javax.xml.bind.annotation.XmlTransient;
-import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.maintenance.ScopeCode;
-import org.apache.sis.internal.metadata.Dependencies;
-import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
-import org.apache.sis.internal.util.CollectionsExt;
 
 
 /**
@@ -79,7 +74,7 @@
      *
      * @see #castOrCopy(Scope)
      */
-    public DefaultScope(final org.opengis.metadata.maintenance.Scope object) {
+    public DefaultScope(final Scope object) {
         super(object);
     }
 
@@ -101,38 +96,10 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultScope castOrCopy(final org.opengis.metadata.maintenance.Scope object) {
+    public static DefaultScope castOrCopy(final Scope object) {
         if (object == null || object instanceof DefaultScope) {
             return (DefaultScope) object;
         }
         return new DefaultScope(object);
     }
-
-    /**
-     * Information about the spatial, vertical and temporal extent of the data specified by the scope.
-     * This method fetches the value from the {@linkplain #getExtents() extents} collection.
-     *
-     * @return information about the extent of the data, or {@code null}.
-     *
-     * @deprecated As of ISO 19115:2014, replaced by {@link #getExtents()}.
-     */
-    @Override
-    @Deprecated
-    @Dependencies("getExtents")
-    public Extent getExtent() {
-        return LegacyPropertyAdapter.getSingleton(getExtents(), Extent.class, null, DefaultScope.class, "getExtent");
-    }
-
-    /**
-     * Sets information about the spatial, vertical and temporal extent of the data specified by the scope.
-     * This method stores the value in the {@linkplain #setExtents(Collection) extents} collection.
-     *
-     * @param  newValue  the new extent.
-     *
-     * @deprecated As of ISO 19115:2014, replaced by {@link #setExtents(Collection)}.
-     */
-    @Deprecated
-    public void setExtent(final Extent newValue) {
-        setExtents(CollectionsExt.singletonOrEmpty(newValue));
-    }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
index c44b351..e546b18 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
@@ -32,6 +32,11 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Axis properties.
@@ -142,8 +147,10 @@
             dimensionName        = object.getDimensionName();
             dimensionSize        = object.getDimensionSize();
             resolution           = object.getResolution();
-            dimensionTitle       = object.getDimensionTitle();
-            dimensionDescription = object.getDimensionDescription();
+            if (object instanceof DefaultDimension) {
+                dimensionTitle       = ((DefaultDimension) object).getDimensionTitle();
+                dimensionDescription = ((DefaultDimension) object).getDimensionDescription();
+            }
         }
     }
 
@@ -254,9 +261,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "dimensionTitle")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="dimensionTitle", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDimensionTitle() {
         return dimensionTitle;
     }
@@ -280,9 +287,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "dimensionDescription")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="dimensionDescription", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDimensionDescription() {
         return dimensionDescription;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
index 036790a..11c6eb6 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
@@ -30,7 +30,7 @@
  * defined in the {@link org.opengis.metadata} package and sub-packages. That standard is identified in SIS by the
  * {@link org.apache.sis.metadata.MetadataStandard#ISO_19115} constant. Other standards are defined as well,
  * for example the {@link org.apache.sis.metadata.MetadataStandard#ISO_19123} constant stands for the standards
- * defined by the interfaces in the {@link org.opengis.coverage} package and sub-packages.
+ * defined by the interfaces in the {@code org.opengis.coverage} package and sub-packages.
  *
  * <p>For each interface, the collection of declared getter methods defines its <cite>properties</cite>
  * (or <cite>attributes</cite>). If a {@link org.opengis.annotation.UML} annotation is attached to the getter method,
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java
index 13e06a9..add4e56 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java
@@ -34,8 +34,8 @@
 import org.apache.sis.internal.util.Numerics;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.citation.ResponsibleParty;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 
 
 /**
@@ -161,9 +161,14 @@
                  */
                 Object value;
                 try {
-                    method = supercede(method);
-                    if (method == null) return null;
+                    final long nb = nullValues;
                     value = fetchValue(source.getLookupInfo(method.getDeclaringClass()), method);
+                    if (value == null) {
+                        nullValues = nb;
+                        method = supercede(method);
+                        if (method == null) return null;
+                        value = fetchValue(source.getLookupInfo(method.getDeclaringClass()), method);
+                    }
                 } catch (ReflectiveOperationException | SQLException | MetadataStoreException e) {
                     throw new BackingStoreException(error(method), e);
                 }
@@ -332,7 +337,7 @@
     private static Method supercede(Method method) throws NoSuchMethodException {
         if (method.getDeclaringClass() == ResponsibleParty.class) {
             if ("getRole".equals(method.getName())) {
-                method = Responsibility.class.getMethod("getRole");
+                method = DefaultResponsibility.class.getMethod("getRole");
             } else {
                 /*
                  * `getIndividualName()`, `getOrganisationName()`, `getPositionName()` and
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java
index be47e90..1272bee 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java
@@ -16,7 +16,7 @@
  */
 package org.apache.sis.metadata.sql;
 
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.PresentationForm;
@@ -81,7 +81,7 @@
         ArgumentChecks.ensureNonNull("type", type);
         ArgumentChecks.ensureNonEmpty("identifier", identifier);
         Object value;
-        if (ControlledVocabulary.class.isAssignableFrom(type)) {
+        if (CodeList.class.isAssignableFrom(type)) {
             value = getCodeList(type, identifier);
         } else {
             value = null;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
index 4cf0c35..996f32f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
@@ -46,7 +46,6 @@
 import java.sql.PreparedStatement;
 import org.opengis.annotation.UML;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.IdentifiedObject;
 import org.apache.sis.metadata.MetadataStandard;
@@ -75,6 +74,9 @@
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.iso.Types;
 
+// Branch-dependent imports
+import org.apache.sis.internal.geoapi.evolution.Interim;
+
 
 /**
  * A connection to a metadata database in read-only mode. It can be either the database
@@ -666,8 +668,8 @@
              * be present in the database in order to ensure foreigner key constraints, but
              * those tables are not used in any way by the org.apache.sis.metadata.sql package.
              */
-            if (metadata instanceof ControlledVocabulary) {
-                identifier = Types.getCodeName((ControlledVocabulary) metadata);
+            if (metadata instanceof CodeList<?>) {
+                identifier = Types.getCodeName((CodeList<?>) metadata);
             } else if (metadata instanceof Enum<?>) {
                 identifier = ((Enum<?>) metadata).name();
             } else {
@@ -733,8 +735,8 @@
              * Note that if a metadata dependency is not found, we can stop the whole process immediately.
              */
             if (value != null) {
-                if (value instanceof ControlledVocabulary) {
-                    value = Types.getCodeName((ControlledVocabulary) value);
+                if (value instanceof CodeList<?>) {
+                    value = Types.getCodeName((CodeList<?>) value);
                 } else if (value instanceof Enum<?>) {
                     value = ((Enum<?>) value).name();
                 } else {
@@ -844,7 +846,7 @@
      *
      * @param  <T>         the parameterized type of the {@code type} argument.
      * @param  type        the interface to implement (e.g. {@link org.opengis.metadata.citation.Citation}),
-     *                     or the {@link ControlledVocabulary} type ({@link CodeList} or some {@link Enum}).
+     *                     or the {@code ControlledVocabulary} type ({@link CodeList} or some {@link Enum}).
      * @param  identifier  the identifier of the record for the metadata entity to be created.
      *                     This is usually the primary key of the record to search for.
      * @return an implementation of the required interface, or the code list element.
@@ -875,7 +877,7 @@
      */
     private Object lookup(final Class<?> type, final String identifier, boolean verify) throws MetadataStoreException {
         Object value;
-        if (ControlledVocabulary.class.isAssignableFrom(type)) {
+        if (CodeList.class.isAssignableFrom(type)) {
             value = getCodeList(type, identifier);
         } else {
             final CacheKey key = new CacheKey(type, identifier);
@@ -976,7 +978,7 @@
          * the name between bracket is a subtype of the given `type` argument.
          */
         final Class<?> type           = TableHierarchy.subType(info.getMetadataType(), toSearch.identifier);
-        final Class<?> returnType     = method.getReturnType();
+        final Class<?> returnType     = Interim.getReturnType(method);
         final boolean  wantCollection = Collection.class.isAssignableFrom(returnType);
         final Class<?> elementType    = wantCollection ? Classes.boundOfParameterizedProperty(method) : returnType;
         final boolean  isMetadata     = standard.isMetadata(elementType);
@@ -1060,12 +1062,8 @@
      * message when the actual class is unknown (it must have been checked dynamically by the caller however).
      */
     @SuppressWarnings("unchecked")
-    static ControlledVocabulary getCodeList(final Class<?> type, final String name) {
-        if (type.isEnum()) {
-            return (ControlledVocabulary) Types.forEnumName(type.asSubclass(Enum.class), name);
-        } else {
-            return Types.forCodeName(type.asSubclass(CodeList.class), name, true);
-        }
+    static CodeList<?> getCodeList(final Class<?> type, final String name) {
+        return Types.forCodeName(type.asSubclass(CodeList.class), name, true);
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
index 4343c03..2b0b649 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
@@ -33,6 +33,7 @@
 import javax.sql.DataSource;
 import java.lang.reflect.Modifier;
 
+import org.opengis.util.CodeList;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.util.FactoryException;
@@ -56,7 +57,7 @@
 import org.apache.sis.xml.IdentifiedObject;
 
 // Branch-dependent imports
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -195,8 +196,8 @@
                 boolean success = false;
                 try {
                     try (Statement stmt = connection.createStatement()) {
-                        if (metadata instanceof ControlledVocabulary) {
-                            identifier = addCode(stmt, (ControlledVocabulary) metadata);
+                        if (metadata instanceof CodeList<?>) {
+                            identifier = addCode(stmt, (CodeList<?>) metadata);
                         } else {
                             identifier = add(stmt, metadata, new IdentityHashMap<>(), null);
                         }
@@ -320,7 +321,7 @@
                  */
                 int maxLength = maximumValueLength;
                 Class<?> rt = colTypes.get(column);
-                final boolean isCodeList = ControlledVocabulary.class.isAssignableFrom(rt);     // Accept also some enums.
+                final boolean isCodeList = CodeList.class.isAssignableFrom(rt);
                 if (isCodeList || standard.isMetadata(rt)) {
                     /*
                      * Found a reference to an other metadata. Remind that column for creating a foreign key
@@ -410,8 +411,8 @@
         for (final Map.Entry<String,Object> entry : asSingletons.entrySet()) {
             Object value = entry.getValue();
             final Class<?> type = value.getClass();
-            if (ControlledVocabulary.class.isAssignableFrom(type)) {
-                value = addCode(stmt, (ControlledVocabulary) value);
+            if (CodeList.class.isAssignableFrom(type)) {
+                value = addCode(stmt, (CodeList<?>) value);
             } else if (type.isEnum()) {
                 value = ((Enum<?>) value).name();
             } else if (standard.isMetadata(type)) {
@@ -488,7 +489,7 @@
             for (final Map.Entry<String,FKey> entry : foreigners.entrySet()) {
                 final FKey fkey = entry.getValue();
                 Class<?> rt = fkey.tableType;
-                final boolean isCodeList = ControlledVocabulary.class.isAssignableFrom(rt);
+                final boolean isCodeList = CodeList.class.isAssignableFrom(rt);
                 final String primaryKey;
                 if (isCodeList) {
                     primaryKey = CODE_COLUMN;
@@ -648,11 +649,9 @@
     /**
      * Adds a code list if it is not already present. This is used only in order to enforce
      * foreigner key constraints in the database. The value of CodeList tables are not used
-     * at parsing time. Enumerations are handled as if they were CodeLists; we do not use
-     * the native SQL {@code ENUM} type for making easier to add new values when a standard
-     * is updated.
+     * at parsing time.
      */
-    private String addCode(final Statement stmt, final ControlledVocabulary code) throws SQLException {
+    private String addCode(final Statement stmt, final CodeList<?> code) throws SQLException {
         assert Thread.holdsLock(this);
         final String table = getTableName(code.getClass());
         final Set<String> columns = getExistingColumns(table);
@@ -707,9 +706,11 @@
         for (final Identifier id : identifiers) {
             identifier = Strings.trimOrNull(id.getCode());
             if (identifier != null) {
-                final String cs = Strings.trimOrNull(id.getCodeSpace());
-                if (cs != null) {
-                    identifier = cs + Constants.DEFAULT_SEPARATOR + identifier;
+                if (id instanceof ReferenceIdentifier) {
+                    final String cs = Strings.trimOrNull(((ReferenceIdentifier) id).getCodeSpace());
+                    if (cs != null) {
+                        identifier = cs + Constants.DEFAULT_SEPARATOR + identifier;
+                    }
                 }
                 break;
             }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
index 92fcc67..fdfb57f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
@@ -226,7 +226,6 @@
      *
      * @see Names#createMemberName(CharSequence, String, CharSequence, Class)
      */
-    @Override
     public MemberName createMemberName(final NameSpace scope, final CharSequence name, final TypeName attributeType) {
         return pool.unique(new DefaultMemberName(scope, name, attributeType));
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecord.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecord.java
index b473aa7..1e6f47d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecord.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecord.java
@@ -176,9 +176,25 @@
      *
      * @return the dictionary of all (<var>name</var>, <var>value</var>) pairs in this record.
      *
-     * @see RecordType#getFieldTypes()
+     * @see RecordType#getMemberTypes()
+     *
+     * @deprecated Renamed {@link #getFields()} for consistency with the 2015 revision of ISO 19103 standard.
      */
     @Override
+    @Deprecated
+    public Map<MemberName, Object> getAttributes() {
+        return getFields();
+    }
+
+    /**
+     * Returns the dictionary of all (<var>name</var>, <var>value</var>) pairs in this record.
+     * This method returns a view which will delegate all {@code get} and {@code put} operations to
+     * the {@link #locate(MemberName)} and {@link #set(MemberName, Object)} methods respectively.
+     *
+     * @return the dictionary of all (<var>name</var>, <var>value</var>) pairs in this record.
+     *
+     * @since 1.1
+     */
     public Map<MemberName, Object> getFields() {
         if (values == null) {                         // Should never be null, except temporarily at XML unmarshalling time.
             return Collections.emptyMap();
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
index 7046f7f..e20dfe2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
@@ -29,7 +29,6 @@
 import org.opengis.util.NameSpace;
 import org.opengis.util.RecordSchema;
 import org.opengis.util.RecordType;
-import org.opengis.feature.AttributeType;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ObjectConverter;
 import org.apache.sis.util.ObjectConverters;
@@ -83,14 +82,13 @@
     /**
      * The factory to use for creating names.
      * This is the factory given at construction time.
+     *
+     * <div class="warning"><b>Upcoming API change</b> — generalization<br>
+     * This field type will be changed to the {@link NameFactory} interface when that interface
+     * will provide a {@code createMemberName(…)} method (tentatively in GeoAPI 3.1).
+     * </div>
      */
-    protected final NameFactory nameFactory;
-
-    /**
-     * The helper class to use for mapping Java classes to {@code TypeName} instances, or {@code null} if not needed.
-     * This helper class is needed only if {@link #nameFactory} is not an instance of {@link DefaultNameFactory}.
-     */
-    private final TypeNames typeFactory;
+    protected final DefaultNameFactory nameFactory;
 
     /**
      * The namespace of {@link RecordType} to be created by this class.
@@ -122,17 +120,21 @@
     /**
      * Creates a new schema of the given name.
      *
+     * <div class="warning"><b>Upcoming API change</b> — generalization<br>
+     * This type of the first argument will be changed to the {@link NameFactory} interface when
+     * that interface will provide a {@code createMemberName(…)} method (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param nameFactory  the factory to use for creating names, or {@code null} for the default factory.
      * @param parent       the parent namespace, or {@code null} if none.
      * @param schemaName   the name of the new schema.
      */
-    public DefaultRecordSchema(NameFactory nameFactory, final NameSpace parent, final CharSequence schemaName) {
+    public DefaultRecordSchema(DefaultNameFactory nameFactory, final NameSpace parent, final CharSequence schemaName) {
         ArgumentChecks.ensureNonNull("schemaName", schemaName);
         if (nameFactory == null) {
-            nameFactory = DefaultFactories.forBuildin(NameFactory.class);
+            nameFactory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
         }
         this.nameFactory    = nameFactory;
-        this.typeFactory    = (nameFactory instanceof DefaultNameFactory) ? null : new TypeNames(nameFactory);
         this.namespace      = nameFactory.createNameSpace(nameFactory.createLocalName(parent, schemaName), null);
         this.description    = new WeakValueHashMap<>(TypeName.class);
         this.attributeTypes = new ConcurrentHashMap<>();
@@ -177,7 +179,7 @@
          * If a record type already exists for the given name, verify that it contains the same fields.
          */
         final Iterator<Map.Entry<CharSequence,Class<?>>> it1 = fields.entrySet().iterator();
-        final Iterator<Map.Entry<MemberName,Type>> it2 = record.getFieldTypes().entrySet().iterator();
+        final Iterator<Map.Entry<MemberName,Type>> it2 = record.getMemberTypes().entrySet().iterator();
         boolean hasNext;
         while ((hasNext = it1.hasNext()) == it2.hasNext()) {
             if (!hasNext) {
@@ -188,7 +190,7 @@
             if (!e2.getKey().tip().toString().equals(e1.toString())) {
                 break;                                                  // Member names differ.
             }
-            if (!((AttributeType) e2.getValue()).getValueClass().equals(e1.getValue())) {
+            if (!((SimpleAttributeType) e2.getValue()).getValueClass().equals(e1.getValue())) {
                 break;                                                  // Value classes differ.
             }
         }
@@ -212,12 +214,7 @@
             if (valueClass == Void.TYPE) {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "valueClass", "void"));
             }
-            final TypeName name;
-            if (nameFactory instanceof DefaultNameFactory) {
-                name = ((DefaultNameFactory) nameFactory).toTypeName(valueClass);
-            } else {
-                name = typeFactory.toTypeName(nameFactory, valueClass);
-            }
+            final TypeName name = nameFactory.toTypeName(valueClass);
             type = new SimpleAttributeType<>(name, valueClass);
             final Type old = attributeTypes.putIfAbsent(valueClass, type);
             if (old != null) {      // May happen if the type has been computed concurrently.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java
index 60d6bfc..7b25c5a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java
@@ -34,7 +34,6 @@
 import org.opengis.util.MemberName;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameSpace;
-import org.opengis.util.NameFactory;
 import org.opengis.util.Record;
 import org.opengis.util.RecordType;
 import org.opengis.util.RecordSchema;
@@ -134,7 +133,7 @@
     public DefaultRecordType(final RecordType other) {
         typeName   = other.getTypeName();
         container  = other.getContainer();
-        fieldTypes = computeTransientFields(other.getFieldTypes());
+        fieldTypes = computeTransientFields(other.getMemberTypes());
     }
 
     /**
@@ -195,7 +194,7 @@
      * @param nameFactory  the factory to use for instantiating {@link MemberName}.
      */
     DefaultRecordType(final TypeName typeName, final RecordSchema container,
-            final Map<? extends CharSequence, ? extends Type> fields, final NameFactory nameFactory)
+            final Map<? extends CharSequence, ? extends Type> fields, final DefaultNameFactory nameFactory)
     {
         this.typeName  = typeName;
         this.container = container;
@@ -331,8 +330,28 @@
      * </div>
      *
      * @return the dictionary of (<var>name</var>, <var>type</var>) pairs, or an empty map if none.
+     *
+     * @deprecated Renamed {@link #getFieldTypes()} for consistency with the 2015 revision of ISO 19103 standard.
      */
     @Override
+    @Deprecated
+    public Map<MemberName,Type> getMemberTypes() {
+        return getFieldTypes();
+    }
+
+    /**
+     * Returns the dictionary of all (<var>name</var>, <var>type</var>) pairs in this record type.
+     * The returned map is unmodifiable.
+     *
+     * <div class="note"><b>Comparison with Java reflection:</b>
+     * If we think about this {@code RecordType} as equivalent to a {@code Class} instance, then
+     * this method can be though as the related to the Java {@link Class#getFields()} method.
+     * </div>
+     *
+     * @return the dictionary of (<var>name</var>, <var>type</var>) pairs, or an empty map if none.
+     *
+     * @since 1.1
+     */
     public Map<MemberName,Type> getFieldTypes() {
         return ObjectConverters.derivedValues(fieldIndices(), MemberName.class, new SurjectiveConverter<Integer,Type>() {
             @Override public Class<Integer> getSourceClass() {return Integer.class;}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java
index cb8a8d1..0186232 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java
@@ -26,12 +26,12 @@
 import org.opengis.util.Type;
 import org.opengis.util.RecordType;
 import org.opengis.util.MemberName;
-import org.opengis.feature.AttributeType;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.internal.simple.SimpleAttributeType;
 
 
 /**
@@ -77,7 +77,7 @@
          */
         Adapter(final RecordType recordType) {
             this.recordType = recordType;
-            computeTransientFields(recordType.getFieldTypes());
+            computeTransientFields(recordType.getMemberTypes());
         }
 
         /**
@@ -89,7 +89,7 @@
          */
         private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
             in.defaultReadObject();
-            computeTransientFields(recordType.getFieldTypes());
+            computeTransientFields(recordType.getMemberTypes());
         }
 
         /**
@@ -152,8 +152,8 @@
         int i = 0;
         for (final Map.Entry<? extends MemberName, ? extends Type> entry : fieldTypes.entrySet()) {
             final Type type = entry.getValue();
-            if (type instanceof AttributeType) {
-                final Class<?> c = ((AttributeType) type).getValueClass();
+            if (type instanceof SimpleAttributeType) {
+                final Class<?> c = ((SimpleAttributeType) type).getValueClass();
                 if (c != Object.class) {
                     if (valueClasses == null) {
                         valueClasses = new Class<?>[size];
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Types.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Types.java
index 7fa0879..5b11cb0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Types.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Types.java
@@ -30,7 +30,6 @@
 import java.io.InputStream;
 import org.opengis.annotation.UML;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.util.DefaultInternationalString;
@@ -57,14 +56,14 @@
  *
  * <ul>
  *   <li>Methods for fetching the ISO name or description of a code list:<ul>
- *     <li>{@link #getStandardName(Class)}            for ISO name</li>
- *     <li>{@link #getListName(ControlledVocabulary)} for ISO name</li>
- *     <li>{@link #getDescription(Class)}             for a description</li>
+ *     <li>{@link #getStandardName(Class)}   for ISO name</li>
+ *     <li>{@link #getListName(CodeList)}    for ISO name</li>
+ *     <li>{@link #getDescription(Class)}    for a description</li>
  *   </ul></li>
  *   <li>Methods for fetching the ISO name or description of a code value:<ul>
- *     <li>{@link #getCodeName(ControlledVocabulary)}    for ISO name,</li>
- *     <li>{@link #getCodeTitle(ControlledVocabulary)}   for a label or title</li>
- *     <li>{@link #getDescription(ControlledVocabulary)} for a more verbose description</li>
+ *     <li>{@link #getCodeName(CodeList)}    for ISO name,</li>
+ *     <li>{@link #getCodeTitle(CodeList)}   for a label or title</li>
+ *     <li>{@link #getDescription(CodeList)} for a more verbose description</li>
  *   </ul></li>
  *   <li>Methods for fetching an instance from a name (converse of above {@code get} methods):<ul>
  *     <li>{@link #forCodeName(Class, String, boolean)}</li>
@@ -94,7 +93,7 @@
  * Such substitution can be done with:
  *
  * <ul>
- *   <li>{@link #getCodeTitle(ControlledVocabulary)} for getting the {@link InternationalString} instance
+ *   <li>{@link #getCodeTitle(CodeList)} for getting the {@link InternationalString} instance
  *       to store in a metadata property.</li>
  *   <li>{@link #forCodeTitle(CharSequence)} for retrieving the {@link CodeList} previously stored as an
  *       {@code InternationalString}.</li>
@@ -185,11 +184,11 @@
      * @param  code  the code for which to get the class name, or {@code null}.
      * @return the ISO (preferred) or Java (fallback) class name, or {@code null} if the given code is null.
      */
-    public static String getListName(final ControlledVocabulary code) {
+    public static String getListName(final CodeList<?> code) {
         if (code == null) {
             return null;
         }
-        final Class<?> type = (code instanceof Enum<?>) ? ((Enum<?>) code).getDeclaringClass() : code.getClass();
+        final Class<?> type = code.getClass();
         final String id = getStandardName(type);
         return (id != null) ? id : type.getSimpleName();
     }
@@ -210,12 +209,12 @@
      * @param  code  the code for which to get the name, or {@code null}.
      * @return the UML identifiers or programmatic name for the given code, or {@code null} if the given code is null.
      *
-     * @see #getCodeLabel(ControlledVocabulary)
-     * @see #getCodeTitle(ControlledVocabulary)
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getCodeLabel(CodeList)
+     * @see #getCodeTitle(CodeList)
+     * @see #getDescription(CodeList)
      * @see #forCodeName(Class, String, boolean)
      */
-    public static String getCodeName(final ControlledVocabulary code) {
+    public static String getCodeName(final CodeList<?> code) {
         if (code == null) {
             return null;
         }
@@ -227,7 +226,7 @@
      * Returns a unlocalized title for the given enumeration or code list value.
      * This method builds a title using heuristics rules, which should give reasonable
      * results without the need of resource bundles. For better results, consider using
-     * {@link #getCodeTitle(ControlledVocabulary)} instead.
+     * {@link #getCodeTitle(CodeList)} instead.
      *
      * <p>The current heuristic implementation iterates over {@linkplain CodeList#names() all code names},
      * selects the longest one excluding the {@linkplain CodeList#name() field name} if possible, then
@@ -244,11 +243,11 @@
      * @param  code  the code from which to get a title, or {@code null}.
      * @return a unlocalized title for the given code, or {@code null} if the given code is null.
      *
-     * @see #getCodeName(ControlledVocabulary)
-     * @see #getCodeTitle(ControlledVocabulary)
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getCodeName(CodeList)
+     * @see #getCodeTitle(CodeList)
+     * @see #getDescription(CodeList)
      */
-    public static String getCodeLabel(final ControlledVocabulary code) {
+    public static String getCodeLabel(final CodeList<?> code) {
         if (code == null) {
             return null;
         }
@@ -267,7 +266,7 @@
 
     /**
      * Returns the title of the given enumeration or code list value. Title are usually much shorter than descriptions.
-     * English titles are often the same than the {@linkplain #getCodeLabel(ControlledVocabulary) code labels}.
+     * English titles are often the same than the {@linkplain #getCodeLabel(CodeList) code labels}.
      *
      * <p>The code or enumeration value given in argument to this method can be retrieved from the returned title
      * with the {@link #forCodeTitle(CharSequence)} method. See <cite>Substituting a free text by a code list</cite>
@@ -276,10 +275,10 @@
      * @param  code  the code for which to get the title, or {@code null}.
      * @return the title, or {@code null} if the given code is null.
      *
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getDescription(CodeList)
      * @see #forCodeTitle(CharSequence)
      */
-    public static InternationalString getCodeTitle(final ControlledVocabulary code) {
+    public static InternationalString getCodeTitle(final CodeList<?> code) {
         return (code != null) ? new CodeTitle(code) : null;
     }
 
@@ -291,10 +290,10 @@
      * @param  code  the code for which to get the localized description, or {@code null}.
      * @return the description, or {@code null} if none or if the given code is null.
      *
-     * @see #getCodeTitle(ControlledVocabulary)
+     * @see #getCodeTitle(CodeList)
      * @see #getDescription(Class)
      */
-    public static InternationalString getDescription(final ControlledVocabulary code) {
+    public static InternationalString getDescription(final CodeList<?> code) {
         if (code != null) {
             final String resources = getResources(code.getClass().getName());
             if (resources != null) {
@@ -311,7 +310,7 @@
      * @param  type  the GeoAPI interface or code list from which to get the description, or {@code null}.
      * @return the description, or {@code null} if none or if the given type is {@code null}.
      *
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getDescription(CodeList)
      */
     public static InternationalString getDescription(final Class<?> type) {
         final String name = getStandardName(type);
@@ -410,7 +409,7 @@
         /**
          * Returns the resource key for the given code list.
          */
-        static String resourceKey(final ControlledVocabulary code) {
+        static String resourceKey(final CodeList<?> code) {
             String key = getCodeName(code);
             if (key.indexOf(SEPARATOR) < 0) {
                 key = getListName(code) + SEPARATOR + key;
@@ -440,14 +439,14 @@
         /**
          * The code list for which to create a title.
          */
-        final ControlledVocabulary code;
+        final CodeList<?> code;
 
         /**
          * Creates a new international string for the given code list element.
          *
          * @param  code  the code list for which to create a title.
          */
-        CodeTitle(final ControlledVocabulary code) {
+        CodeTitle(final CodeList<?> code) {
             super(CodeLists.RESOURCES, resourceKey(code));
             this.code = code;
         }
@@ -477,21 +476,17 @@
     }
 
     /**
-     * Returns all known values for the given type of code list or enumeration.
+     * Returns all known values for the given type of code list.
      * Note that the size of the returned array may growth between different invocations of this method,
      * since users can add their own codes to an existing list.
      *
-     * <div class="note"><b>Note:</b>
-     * This method works with both {@link Enum} and {@link CodeList}. However if the type is known to be an
-     * {@code Enum}, then the standard {@link Class#getEnumConstants()} method is more efficient.</div>
-     *
      * @param  <T>       the compile-time type given as the {@code codeType} parameter.
-     * @param  codeType  the type of code list or enumeration.
-     * @return the list of values for the given code list or enumeration, or an empty array if none.
+     * @param  codeType  the type of code list.
+     * @return the list of values for the given code list, or an empty array if none.
      *
      * @see Class#getEnumConstants()
      */
-    public static <T extends ControlledVocabulary> T[] getCodeValues(final Class<T> codeType) {
+    public static <T extends CodeList<?>> T[] getCodeValues(final Class<T> codeType) {
         return CodeLists.values(codeType);
     }
 
@@ -532,7 +527,7 @@
             return null;
         }
         if (typeForNames == null) {
-            final Class<UML> c = UML.class;
+            final Class<Types> c = Types.class;
             final InputStream in = c.getResourceAsStream("class-index.properties");
             if (in == null) {
                 throw new MissingResourceException("class-index.properties", c.getName(), identifier);
@@ -629,7 +624,7 @@
      * @return a code matching the given name, or {@code null} if the name is null
      *         or if no matching code is found and {@code canCreate} is {@code false}.
      *
-     * @see #getCodeName(ControlledVocabulary)
+     * @see #getCodeName(CodeList)
      * @see CodeList#valueOf(Class, String)
      */
     public static <T extends CodeList<T>> T forCodeName(final Class<T> codeType, String name, final boolean canCreate) {
@@ -641,7 +636,7 @@
      * The current implementation performs the following choice:
      *
      * <ul>
-     *   <li>If the given title is a value returned by a previous call to {@link #getCodeTitle(ControlledVocabulary)},
+     *   <li>If the given title is a value returned by a previous call to {@link #getCodeTitle(CodeList)},
      *       returns the code or enumeration value used for creating that title.</li>
      *   <li>Otherwise returns {@code null}.</li>
      * </ul>
@@ -649,11 +644,11 @@
      * @param  title  the title for which to get a code or enumeration value, or {@code null}.
      * @return the code or enumeration value associated with the given title, or {@code null}.
      *
-     * @see #getCodeTitle(ControlledVocabulary)
+     * @see #getCodeTitle(CodeList)
      *
      * @since 0.7
      */
-    public static ControlledVocabulary forCodeTitle(final CharSequence title) {
+    public static CodeList<?> forCodeTitle(final CharSequence title) {
         return (title instanceof CodeTitle) ? ((CodeTitle) title).code : null;
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/LegacyCodes.java b/core/sis-metadata/src/main/java/org/apache/sis/xml/LegacyCodes.java
index 7408ac1..65f4f7e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/xml/LegacyCodes.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/LegacyCodes.java
@@ -19,15 +19,8 @@
 import java.util.Map;
 import java.util.HashMap;
 import java.util.Locale;
-import java.util.Properties;
-import java.io.InputStream;
-import java.io.IOException;
-import org.opengis.metadata.Metadata;
-import org.apache.sis.internal.system.Loggers;
+import org.opengis.metadata.identification.CharacterSet;
 import org.apache.sis.util.collection.Containers;
-import org.apache.sis.util.logging.Logging;
-
-import static java.util.logging.Logger.getLogger;
 
 
 /**
@@ -52,18 +45,15 @@
      */
     static final Map<String,String> IANA_TO_LEGACY, LEGACY_TO_IANA;
     static {
-        final Properties codes = new Properties();
-        try (InputStream in = Metadata.class.getResourceAsStream("2003/charset-codes.properties")) {
-            codes.load(in);
-        } catch (IOException e) {
-            Logging.unexpectedException(getLogger(Loggers.XML), ValueConverter.class, "toCharset[Code]", e);
-        }
-        final int capacity = Containers.hashMapCapacity(codes.size());
+        final CharacterSet[] codes = CharacterSet.values();
+        final int capacity = Containers.hashMapCapacity(codes.length);
         IANA_TO_LEGACY = new HashMap<>(capacity);
         LEGACY_TO_IANA = new HashMap<>(capacity);
-        for (final Map.Entry<Object,Object> entry : codes.entrySet()) {
-            final String legacy = ((String) entry.getKey()).intern();
-            final String name   = ((String) entry.getValue()).intern();
+        for (final CharacterSet code : codes) {
+            final String   legacy = code.identifier().intern();
+            final String[] names  = code.names();
+            String name = names[names.length - 1];
+            if (name.equals("ebcdic")) name = "EBCDIC"; // Missing IANA name in GeoAPI CharacterSet.
             IANA_TO_LEGACY.put(name  .toUpperCase(Locale.US), legacy); // IANA names are restricted to US-ASCII.
             LEGACY_TO_IANA.put(legacy.toLowerCase(Locale.US), name);
             IANA_TO_LEGACY.put(name, legacy);
diff --git a/core/sis-metadata/src/main/resources/org/apache/sis/util/iso/class-index.properties b/core/sis-metadata/src/main/resources/org/apache/sis/util/iso/class-index.properties
new file mode 100644
index 0000000..e44a58b
--- /dev/null
+++ b/core/sis-metadata/src/main/resources/org/apache/sis/util/iso/class-index.properties
@@ -0,0 +1,229 @@
+#
+# This is an automatically generated file. The same content is provided by GeoAPI 3.1
+# in the org.opengis.annotation package. Since this file does not exist in GeoAPI 3.0,
+# the Apache SIS branch for GeoAPI 3.0 maintains this copy (provided by the original
+# author).
+#
+CC_ConcatenatedOperation=org.opengis.referencing.operation.ConcatenatedOperation
+CC_Conversion=org.opengis.referencing.operation.Conversion
+CC_CoordinateOperation=org.opengis.referencing.operation.CoordinateOperation
+CC_Formula=org.opengis.referencing.operation.Formula
+CC_GeneralOperationParameter=org.opengis.parameter.GeneralParameterDescriptor
+CC_GeneralParameterValue=org.opengis.parameter.GeneralParameterValue
+CC_OperationMethod=org.opengis.referencing.operation.OperationMethod
+CC_OperationParameter=org.opengis.parameter.ParameterDescriptor
+CC_OperationParameterGroup=org.opengis.parameter.ParameterDescriptorGroup
+CC_ParameterValue=org.opengis.parameter.ParameterValue
+CC_ParameterValueGroup=org.opengis.parameter.ParameterValueGroup
+CC_PassThroughOperation=org.opengis.referencing.operation.PassThroughOperation
+CC_SingleOperation=org.opengis.referencing.operation.SingleOperation
+CC_Transformation=org.opengis.referencing.operation.Transformation
+CD_Datum=org.opengis.referencing.datum.Datum
+CD_Ellipsoid=org.opengis.referencing.datum.Ellipsoid
+CD_EngineeringDatum=org.opengis.referencing.datum.EngineeringDatum
+CD_GeodeticDatum=org.opengis.referencing.datum.GeodeticDatum
+CD_ImageDatum=org.opengis.referencing.datum.ImageDatum
+CD_PixelInCell=org.opengis.referencing.datum.PixelInCell
+CD_PrimeMeridian=org.opengis.referencing.datum.PrimeMeridian
+CD_TemporalDatum=org.opengis.referencing.datum.TemporalDatum
+CD_VerticalDatum=org.opengis.referencing.datum.VerticalDatum
+CD_VerticalDatumType=org.opengis.referencing.datum.VerticalDatumType
+CI_Address=org.opengis.metadata.citation.Address
+CI_Citation=org.opengis.metadata.citation.Citation
+CI_Contact=org.opengis.metadata.citation.Contact
+CI_Date=org.opengis.metadata.citation.CitationDate
+CI_DateTypeCode=org.opengis.metadata.citation.DateType
+CI_OnLineFunctionCode=org.opengis.metadata.citation.OnLineFunction
+CI_OnlineResource=org.opengis.metadata.citation.OnlineResource
+CI_PresentationFormCode=org.opengis.metadata.citation.PresentationForm
+CI_ResponsibleParty=org.opengis.metadata.citation.ResponsibleParty
+CI_RoleCode=org.opengis.metadata.citation.Role
+CI_Series=org.opengis.metadata.citation.Series
+CI_Telephone=org.opengis.metadata.citation.Telephone
+CS_AffineCS=org.opengis.referencing.cs.AffineCS
+CS_AxisDirection=org.opengis.referencing.cs.AxisDirection
+CS_CartesianCS=org.opengis.referencing.cs.CartesianCS
+CS_CoordinateSystem=org.opengis.referencing.cs.CoordinateSystem
+CS_CoordinateSystemAxis=org.opengis.referencing.cs.CoordinateSystemAxis
+CS_CylindricalCS=org.opengis.referencing.cs.CylindricalCS
+CS_EllipsoidalCS=org.opengis.referencing.cs.EllipsoidalCS
+CS_LinearCS=org.opengis.referencing.cs.LinearCS
+CS_PolarCS=org.opengis.referencing.cs.PolarCS
+CS_RangeMeaning=org.opengis.referencing.cs.RangeMeaning
+CS_SphericalCS=org.opengis.referencing.cs.SphericalCS
+CS_TimeCS=org.opengis.referencing.cs.TimeCS
+CS_UserDefinedCS=org.opengis.referencing.cs.UserDefinedCS
+CS_VerticalCS=org.opengis.referencing.cs.VerticalCS
+DQ_AbsoluteExternalPositionalAccuracy=org.opengis.metadata.quality.AbsoluteExternalPositionalAccuracy
+DQ_AccuracyOfATimeMeasurement=org.opengis.metadata.quality.AccuracyOfATimeMeasurement
+DQ_Completeness=org.opengis.metadata.quality.Completeness
+DQ_CompletenessCommission=org.opengis.metadata.quality.CompletenessCommission
+DQ_CompletenessOmission=org.opengis.metadata.quality.CompletenessOmission
+DQ_ConceptualConsistency=org.opengis.metadata.quality.ConceptualConsistency
+DQ_ConformanceResult=org.opengis.metadata.quality.ConformanceResult
+DQ_DataQuality=org.opengis.metadata.quality.DataQuality
+DQ_DomainConsistency=org.opengis.metadata.quality.DomainConsistency
+DQ_Element=org.opengis.metadata.quality.Element
+DQ_EvaluationMethodTypeCode=org.opengis.metadata.quality.EvaluationMethodType
+DQ_FormatConsistency=org.opengis.metadata.quality.FormatConsistency
+DQ_GriddedDataPositionalAccuracy=org.opengis.metadata.quality.GriddedDataPositionalAccuracy
+DQ_LogicalConsistency=org.opengis.metadata.quality.LogicalConsistency
+DQ_NonQuantitativeAttributeAccuracy=org.opengis.metadata.quality.NonQuantitativeAttributeAccuracy
+DQ_PositionalAccuracy=org.opengis.metadata.quality.PositionalAccuracy
+DQ_QuantitativeAttributeAccuracy=org.opengis.metadata.quality.QuantitativeAttributeAccuracy
+DQ_QuantitativeResult=org.opengis.metadata.quality.QuantitativeResult
+DQ_RelativeInternalPositionalAccuracy=org.opengis.metadata.quality.RelativeInternalPositionalAccuracy
+DQ_Result=org.opengis.metadata.quality.Result
+DQ_Scope=org.opengis.metadata.quality.Scope
+DQ_TemporalAccuracy=org.opengis.metadata.quality.TemporalAccuracy
+DQ_TemporalConsistency=org.opengis.metadata.quality.TemporalConsistency
+DQ_TemporalValidity=org.opengis.metadata.quality.TemporalValidity
+DQ_ThematicAccuracy=org.opengis.metadata.quality.ThematicAccuracy
+DQ_ThematicClassificationCorrectness=org.opengis.metadata.quality.ThematicClassificationCorrectness
+DQ_TopologicalConsistency=org.opengis.metadata.quality.TopologicalConsistency
+DS_AssociationTypeCode=org.opengis.metadata.identification.AssociationType
+DS_InitiativeTypeCode=org.opengis.metadata.identification.InitiativeType
+EX_BoundingPolygon=org.opengis.metadata.extent.BoundingPolygon
+EX_Extent=org.opengis.metadata.extent.Extent
+EX_GeographicBoundingBox=org.opengis.metadata.extent.GeographicBoundingBox
+EX_GeographicDescription=org.opengis.metadata.extent.GeographicDescription
+EX_GeographicExtent=org.opengis.metadata.extent.GeographicExtent
+EX_SpatialTemporalExtent=org.opengis.metadata.extent.SpatialTemporalExtent
+EX_TemporalExtent=org.opengis.metadata.extent.TemporalExtent
+EX_VerticalExtent=org.opengis.metadata.extent.VerticalExtent
+IO_IdentifiedObject=org.opengis.referencing.IdentifiedObject
+LE_Algorithm=org.opengis.metadata.lineage.Algorithm
+LE_NominalResolution=org.opengis.metadata.lineage.NominalResolution
+LE_ProcessStep=org.opengis.metadata.lineage.ProcessStep
+LE_ProcessStepReport=org.opengis.metadata.lineage.ProcessStepReport
+LE_Processing=org.opengis.metadata.lineage.Processing
+LE_Source=org.opengis.metadata.lineage.Source
+LI_Lineage=org.opengis.metadata.lineage.Lineage
+LI_ProcessStep=org.opengis.metadata.lineage.ProcessStep
+LI_Source=org.opengis.metadata.lineage.Source
+MD_AggregateInformation=org.opengis.metadata.identification.AggregateInformation
+MD_ApplicationSchemaInformation=org.opengis.metadata.ApplicationSchemaInformation
+MD_Band=org.opengis.metadata.content.Band
+MD_BrowseGraphic=org.opengis.metadata.identification.BrowseGraphic
+MD_CellGeometryCode=org.opengis.metadata.spatial.CellGeometry
+MD_CharacterSetCode=org.opengis.metadata.identification.CharacterSet
+MD_ClassificationCode=org.opengis.metadata.constraint.Classification
+MD_Constraints=org.opengis.metadata.constraint.Constraints
+MD_ContentInformation=org.opengis.metadata.content.ContentInformation
+MD_CoverageContentTypeCode=org.opengis.metadata.content.CoverageContentType
+MD_CoverageDescription=org.opengis.metadata.content.CoverageDescription
+MD_DataIdentification=org.opengis.metadata.identification.DataIdentification
+MD_DatatypeCode=org.opengis.metadata.Datatype
+MD_DigitalTransferOptions=org.opengis.metadata.distribution.DigitalTransferOptions
+MD_Dimension=org.opengis.metadata.spatial.Dimension
+MD_DimensionNameTypeCode=org.opengis.metadata.spatial.DimensionNameType
+MD_Distribution=org.opengis.metadata.distribution.Distribution
+MD_Distributor=org.opengis.metadata.distribution.Distributor
+MD_ExtendedElementInformation=org.opengis.metadata.ExtendedElementInformation
+MD_FeatureCatalogueDescription=org.opengis.metadata.content.FeatureCatalogueDescription
+MD_FeatureTypeList=org.opengis.metadata.FeatureTypeList
+MD_Format=org.opengis.metadata.distribution.Format
+MD_GeometricObjectTypeCode=org.opengis.metadata.spatial.GeometricObjectType
+MD_GeometricObjects=org.opengis.metadata.spatial.GeometricObjects
+MD_Georectified=org.opengis.metadata.spatial.Georectified
+MD_Georeferenceable=org.opengis.metadata.spatial.Georeferenceable
+MD_GridSpatialRepresentation=org.opengis.metadata.spatial.GridSpatialRepresentation
+MD_Identification=org.opengis.metadata.identification.Identification
+MD_Identifier=org.opengis.metadata.Identifier
+MD_ImageDescription=org.opengis.metadata.content.ImageDescription
+MD_ImagingConditionCode=org.opengis.metadata.content.ImagingCondition
+MD_KeywordTypeCode=org.opengis.metadata.identification.KeywordType
+MD_Keywords=org.opengis.metadata.identification.Keywords
+MD_LegalConstraints=org.opengis.metadata.constraint.LegalConstraints
+MD_MaintenanceFrequencyCode=org.opengis.metadata.maintenance.MaintenanceFrequency
+MD_MaintenanceInformation=org.opengis.metadata.maintenance.MaintenanceInformation
+MD_Medium=org.opengis.metadata.distribution.Medium
+MD_MediumFormatCode=org.opengis.metadata.distribution.MediumFormat
+MD_MediumNameCode=org.opengis.metadata.distribution.MediumName
+MD_Metadata=org.opengis.metadata.Metadata
+MD_MetadataExtensionInformation=org.opengis.metadata.MetadataExtensionInformation
+MD_ObligationCode=org.opengis.metadata.Obligation
+MD_PixelOrientationCode=org.opengis.metadata.spatial.PixelOrientation
+MD_PortrayalCatalogueReference=org.opengis.metadata.PortrayalCatalogueReference
+MD_ProgressCode=org.opengis.metadata.identification.Progress
+MD_RangeDimension=org.opengis.metadata.content.RangeDimension
+MD_RepresentativeFraction=org.opengis.metadata.identification.RepresentativeFraction
+MD_Resolution=org.opengis.metadata.identification.Resolution
+MD_RestrictionCode=org.opengis.metadata.constraint.Restriction
+MD_ScopeCode=org.opengis.metadata.maintenance.ScopeCode
+MD_ScopeDescription=org.opengis.metadata.maintenance.ScopeDescription
+MD_SecurityConstraints=org.opengis.metadata.constraint.SecurityConstraints
+MD_SpatialRepresentation=org.opengis.metadata.spatial.SpatialRepresentation
+MD_SpatialRepresentationTypeCode=org.opengis.metadata.spatial.SpatialRepresentationType
+MD_StandardOrderProcess=org.opengis.metadata.distribution.StandardOrderProcess
+MD_TopicCategoryCode=org.opengis.metadata.identification.TopicCategory
+MD_TopologyLevelCode=org.opengis.metadata.spatial.TopologyLevel
+MD_Usage=org.opengis.metadata.identification.Usage
+MD_VectorSpatialRepresentation=org.opengis.metadata.spatial.VectorSpatialRepresentation
+MI_AcquisitionInformation=org.opengis.metadata.acquisition.AcquisitionInformation
+MI_Band=org.opengis.metadata.content.Band
+MI_BandDefinition=org.opengis.metadata.content.BandDefinition
+MI_ContextCode=org.opengis.metadata.acquisition.Context
+MI_CoverageDescription=org.opengis.metadata.content.CoverageDescription
+MI_EnvironmentalRecord=org.opengis.metadata.acquisition.EnvironmentalRecord
+MI_Event=org.opengis.metadata.acquisition.Event
+MI_GCP=org.opengis.metadata.spatial.GCP
+MI_GCPCollection=org.opengis.metadata.spatial.GCPCollection
+MI_GeolocationInformation=org.opengis.metadata.spatial.GeolocationInformation
+MI_GeometryTypeCode=org.opengis.metadata.acquisition.GeometryType
+MI_Georectified=org.opengis.metadata.spatial.Georectified
+MI_Georeferenceable=org.opengis.metadata.spatial.Georeferenceable
+MI_ImageDescription=org.opengis.metadata.content.ImageDescription
+MI_Instrument=org.opengis.metadata.acquisition.Instrument
+MI_Metadata=org.opengis.metadata.Metadata
+MI_Objective=org.opengis.metadata.acquisition.Objective
+MI_ObjectiveTypeCode=org.opengis.metadata.acquisition.ObjectiveType
+MI_Operation=org.opengis.metadata.acquisition.Operation
+MI_OperationTypeCode=org.opengis.metadata.acquisition.OperationType
+MI_Plan=org.opengis.metadata.acquisition.Plan
+MI_Platform=org.opengis.metadata.acquisition.Platform
+MI_PlatformPass=org.opengis.metadata.acquisition.PlatformPass
+MI_PolarizationOrientationCode=org.opengis.metadata.content.PolarizationOrientation
+MI_PriorityCode=org.opengis.metadata.acquisition.Priority
+MI_RangeElementDescription=org.opengis.metadata.content.RangeElementDescription
+MI_RequestedDate=org.opengis.metadata.acquisition.RequestedDate
+MI_Requirement=org.opengis.metadata.acquisition.Requirement
+MI_SequenceCode=org.opengis.metadata.acquisition.Sequence
+MI_TransferFunctionTypeCode=org.opengis.metadata.content.TransferFunctionType
+MI_TriggerCode=org.opengis.metadata.acquisition.Trigger
+QE_CoverageResult=org.opengis.metadata.quality.CoverageResult
+QE_Usability=org.opengis.metadata.quality.Usability
+RS_Identifier=org.opengis.referencing.ReferenceIdentifier
+RS_ReferenceSystem=org.opengis.referencing.ReferenceSystem
+SC_CRS=org.opengis.referencing.crs.CoordinateReferenceSystem
+SC_CompoundCRS=org.opengis.referencing.crs.CompoundCRS
+SC_DerivedCRS=org.opengis.referencing.crs.DerivedCRS
+SC_EngineeringCRS=org.opengis.referencing.crs.EngineeringCRS
+SC_GeneralDerivedCRS=org.opengis.referencing.crs.GeneralDerivedCRS
+SC_GeocentricCRS=org.opengis.referencing.crs.GeocentricCRS
+SC_GeodeticCRS=org.opengis.referencing.crs.GeodeticCRS
+SC_GeographicCRS=org.opengis.referencing.crs.GeographicCRS
+SC_ImageCRS=org.opengis.referencing.crs.ImageCRS
+SC_ProjectedCRS=org.opengis.referencing.crs.ProjectedCRS
+SC_SingleCRS=org.opengis.referencing.crs.SingleCRS
+SC_TemporalCRS=org.opengis.referencing.crs.TemporalCRS
+SC_VerticalCRS=org.opengis.referencing.crs.VerticalCRS
+SV_ServiceIdentification=org.opengis.metadata.identification.ServiceIdentification
+
+#
+# Additional types not yet defined in GeoAPI.
+#
+CI_Party=org.apache.sis.metadata.iso.citation.AbstractParty
+CI_Individual=org.apache.sis.metadata.iso.citation.DefaultIndividual
+CI_Organisation=org.apache.sis.metadata.iso.citation.DefaultOrganisation
+CI_Responsibility=org.apache.sis.metadata.iso.citation.DefaultResponsibility
+CI_TelephoneTypeCode=org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList
+MD_AssociatedResource=org.apache.sis.metadata.iso.identification.DefaultAssociatedResource
+MD_AttributeGroup=org.apache.sis.metadata.iso.content.DefaultAttributeGroup
+MD_FeatureTypeInfo=org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo
+MD_Releasability=org.apache.sis.metadata.iso.constraint.DefaultReleasability
+MD_SampleDimension=org.apache.sis.metadata.iso.content.DefaultSampleDimension
+MD_MetadataScope=org.apache.sis.metadata.iso.DefaultMetadataScope
+SV_CoupledResource=org.apache.sis.metadata.iso.identification.DefaultCoupledResource
+SV_OperationChainMetadata=org.apache.sis.metadata.iso.identification.DefaultOperationChainMetadata
+SV_OperationMetadata=org.apache.sis.metadata.iso.identification.DefaultOperationMetadata
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java
index 2ea86e8..7977c1b 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java
@@ -24,7 +24,7 @@
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.DateType;
 import org.opengis.metadata.citation.CitationDate;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.citation.PresentationForm;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.internal.xml.LegacyNamespaces;
@@ -101,7 +101,7 @@
     @Test
     public void testDefaultURL() throws JAXBException {
         final String expected = getResponsiblePartyXML(CodeListUID.METADATA_ROOT_LEGACY);
-        final Responsibility rp = unmarshal(Responsibility.class, expected);
+        final ResponsibleParty rp = unmarshal(ResponsibleParty.class, expected);
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, rp.getRole());
         /*
          * Use the convenience method in order to avoid the effort of creating
@@ -121,7 +121,7 @@
     @Test
     public void testLegacyISO_URL() throws JAXBException {
         final String expected = getResponsiblePartyXML("http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/");
-        final Responsibility rp = unmarshal(Responsibility.class, expected);
+        final ResponsibleParty rp = unmarshal(ResponsibleParty.class, expected);
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, rp.getRole());
 
         final MarshallerPool pool = getMarshallerPool();
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java
index 6174f22..65eef90 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java
@@ -38,7 +38,7 @@
 import static org.apache.sis.test.TestUtilities.date;
 import static org.apache.sis.test.TestUtilities.format;
 
-import org.opengis.temporal.Instant;
+import org.apache.sis.internal.geoapi.temporal.Instant;
 
 
 /**
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java
index cb14efb..3e849cc 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java
@@ -36,7 +36,6 @@
 import org.junit.Test;
 
 import static org.apache.sis.test.MetadataAssert.*;
-import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.internal.util.StandardDateFormat.UTC;
 
 
@@ -167,7 +166,7 @@
         final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
         final String xml = getMetadataXML(LANGUAGE_CODE);
         final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
-        assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
+        assertEquals(Locale.JAPANESE, metadata.getLanguage());
     }
 
     /**
@@ -190,7 +189,7 @@
         final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
         final String xml = getMetadataXML(LANGUAGE_CODE_WITHOUT_ATTRIBUTE);
         final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
-        assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
+        assertEquals(Locale.JAPANESE, metadata.getLanguage());
         pool.recycle(unmarshaller);
     }
 
@@ -232,7 +231,7 @@
         final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
         final String xml = getMetadataXML(CHARACTER_STRING);
         final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
-        assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
+        assertEquals(Locale.JAPANESE, metadata.getLanguage());
         pool.recycle(unmarshaller);
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameterTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameterTest.java
index 0159972..d4e0633 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameterTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameterTest.java
@@ -18,7 +18,6 @@
 
 import javax.xml.bind.JAXBException;
 import org.opengis.util.MemberName;
-import org.opengis.parameter.ParameterDirection;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.util.iso.Names;
 import org.apache.sis.test.xml.TestCase;
@@ -48,7 +47,6 @@
         param.memberName    = paramName;
         param.optionality   = true;
         param.repeatability = false;
-        param.direction     = ParameterDirection.IN;
         return param;
     }
 
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/IdentifiersTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/IdentifiersTest.java
index 38dfb93..2f43a2a 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/IdentifiersTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/IdentifiersTest.java
@@ -20,6 +20,7 @@
 import java.util.Arrays;
 import java.util.ArrayList;
 import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.test.TestCase;
@@ -49,8 +50,15 @@
     /**
      * Creates an identifier with a code space.
      */
-    private static Identifier identifier(final String codeSpace, final String code) {
-        return new DefaultIdentifier(codeSpace, code, null);
+    private static ReferenceIdentifier identifier(final String codeSpace, final String code) {
+        return new Id(codeSpace, code);
+    }
+
+    @SuppressWarnings("serial")
+    private static final class Id extends DefaultIdentifier implements ReferenceIdentifier {
+        Id(String codeSpace, String code) {
+            super(codeSpace, code, null);
+        }
     }
 
     /**
@@ -58,8 +66,8 @@
      */
     @Test
     public void testHasCommonIdentifier() {
-        final List<Identifier> id1 = new ArrayList<>(3);
-        final List<Identifier> id2 = new ArrayList<>(2);
+        final List<ReferenceIdentifier> id1 = new ArrayList<>(3);
+        final List<ReferenceIdentifier> id2 = new ArrayList<>(2);
         assertNull(Identifiers.hasCommonIdentifier(id1, id2));
         /*
          * Add codes for two Operation Methods which are implemented in Apache SIS by the same class:
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
index e3ef4bd..e1ffeea 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
@@ -23,7 +23,6 @@
 import java.nio.charset.StandardCharsets;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.content.ContentInformation;
-import org.opengis.metadata.content.CoverageDescription;
 import org.opengis.metadata.content.FeatureCatalogueDescription;
 import org.opengis.metadata.content.ImagingCondition;
 import org.opengis.metadata.content.ImageDescription;
@@ -109,7 +108,7 @@
         final Iterator<ContentInformation> it       = target.getContentInfo().iterator();
         final ImageDescription             image    = (ImageDescription)            it.next();
         final FeatureCatalogueDescription  features = (FeatureCatalogueDescription) it.next();
-        final CoverageDescription          coverage = (CoverageDescription)         it.next();
+        final DefaultCoverageDescription   coverage = (DefaultCoverageDescription)  it.next();
         assertFalse(it.hasNext());
 
         assertEquals("imagingCondition",     ImagingCondition.CLOUD, image   .getImagingCondition());
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java
index d526d0e..c281ab9 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java
@@ -21,7 +21,6 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.Individual;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.acquisition.Instrument;
 import org.opengis.metadata.acquisition.Platform;
@@ -96,7 +95,7 @@
         /*
          * Individual hash code is the sum of all its properties, none of them being a collection.
          */
-        int expected = Individual.class.hashCode() + person.hashCode();
+        int expected = DefaultIndividual.class.hashCode() + person.hashCode();
         assertEquals("Individual", Integer.valueOf(expected), hash(party));
         /*
          * The +31 below come from java.util.List contract, since above Individual is a list member.
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataCopierTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataCopierTest.java
index e2e25d7..4fbf727 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataCopierTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataCopierTest.java
@@ -116,7 +116,7 @@
         final DefaultMetadata original = new DefaultMetadata();
         original.getLocalesAndCharsets().put(Locale.FRENCH,   StandardCharsets.UTF_8);
         original.getLocalesAndCharsets().put(Locale.JAPANESE, StandardCharsets.UTF_16);
-        final Metadata copy = copier.copy(Metadata.class, original);
+        final DefaultMetadata copy = (DefaultMetadata) copier.copy(Metadata.class, original);
         final Map<Locale,Charset> lc = copy.getLocalesAndCharsets();
         assertEquals(StandardCharsets.UTF_8,  lc.get(Locale.FRENCH));
         assertEquals(StandardCharsets.UTF_16, lc.get(Locale.JAPANESE));
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
index db34687..319fd13 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
@@ -18,14 +18,12 @@
 
 import java.util.Set;
 import java.util.Map;
-import java.util.List;
 import java.util.HashSet;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.quality.Completeness;
 import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.coverage.grid.RectifiedGrid;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
 import org.apache.sis.metadata.iso.acquisition.DefaultPlatform;
@@ -94,7 +92,7 @@
         assertFalse("isMetadata(IdentifiedObject)",       isMetadata(IdentifiedObject.class));
         assertFalse("isMetadata(SimpleIdentifiedObject)", isMetadata(SimpleIdentifiedObject.class));
         assertFalse("isMetadata(GeographicCRS)",          isMetadata(GeographicCRS.class));
-        assertFalse("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
+//      assertFalse("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
         assertFalse("isMetadata(Double)",                 isMetadata(Double.class));
         assertFalse("isMetadata(double)",                 isMetadata(Double.TYPE));
 
@@ -105,7 +103,7 @@
         assertTrue ("isMetadata(IdentifiedObject)",       isMetadata(IdentifiedObject.class));
         assertTrue ("isMetadata(SimpleIdentifiedObject)", isMetadata(SimpleIdentifiedObject.class));
         assertTrue ("isMetadata(GeographicCRS)",          isMetadata(GeographicCRS.class));
-        assertFalse("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
+//      assertFalse("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
 
         standard = MetadataStandard.ISO_19123;
         assertFalse("isMetadata(String)",                 isMetadata(String.class));
@@ -114,7 +112,7 @@
         assertTrue ("isMetadata(IdentifiedObject)",       isMetadata(IdentifiedObject.class));       // Dependency
         assertTrue ("isMetadata(SimpleIdentifiedObject)", isMetadata(SimpleIdentifiedObject.class)); // Dependency
         assertTrue ("isMetadata(GeographicCRS)",          isMetadata(GeographicCRS.class));          // Dependency
-        assertTrue ("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
+//      assertTrue ("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
     }
 
     /**
@@ -325,39 +323,6 @@
     }
 
     /**
-     * Tests the {@link MetadataStandard#ISO_19123} constant. Getters shall
-     * be accessible even if there is no implementation on the classpath.
-     */
-    @Test
-    @DependsOnMethod("testGetAccessor")
-    public void testWithoutImplementation() {
-        standard = MetadataStandard.ISO_19123;
-        assertFalse("isMetadata(String)",          isMetadata(String.class));
-        assertTrue ("isMetadata(Citation)",        isMetadata(Citation.class));         // Transitive dependency
-        assertTrue ("isMetadata(DefaultCitation)", isMetadata(DefaultCitation.class));  // Transitive dependency
-        assertTrue ("isMetadata(RectifiedGrid)",   isMetadata(RectifiedGrid.class));
-        /*
-         * Ensure that the getters have been found.
-         */
-        final Map<String,String> names = standard.asNameMap(RectifiedGrid.class, KeyNamePolicy.UML_IDENTIFIER, KeyNamePolicy.JAVABEANS_PROPERTY);
-        assertFalse("Getters should have been found even if there is no implementation.", names.isEmpty());
-        assertEquals("dimension", names.get("dimension"));
-        assertEquals("cells", names.get("cell"));
-        /*
-         * Ensure that the type are recognized, especially RectifiedGrid.getOffsetVectors()
-         * which is of type List<double[]>.
-         */
-        Map<String,Class<?>> types;
-        types = standard.asTypeMap(RectifiedGrid.class, KeyNamePolicy.UML_IDENTIFIER, TypeValuePolicy.PROPERTY_TYPE);
-        assertEquals("The return type is the int primitive type.", Integer.TYPE, types.get("dimension"));
-        assertEquals("The offset vectors are stored in a List.",   List.class,   types.get("offsetVectors"));
-
-        types = standard.asTypeMap(RectifiedGrid.class, KeyNamePolicy.UML_IDENTIFIER, TypeValuePolicy.ELEMENT_TYPE);
-        assertEquals("As elements in a list of dimensions.",       Integer.class,  types.get("dimension"));
-        assertEquals("As elements in the list of offset vectors.", double[].class, types.get("offsetVectors"));
-    }
-
-    /**
      * Tests serialization of pre-defined constants.
      */
     @Test
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
index 1816da1..79d37af 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
@@ -35,7 +35,6 @@
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.distribution.Format;
 import org.opengis.metadata.constraint.Constraints;
-import org.opengis.metadata.content.AttributeGroup;
 import org.opengis.metadata.content.CoverageContentType;
 import org.opengis.metadata.content.CoverageDescription;
 import org.opengis.metadata.identification.*;                       // Really using almost everything.
@@ -50,13 +49,15 @@
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.util.InternationalString;
 import org.opengis.util.GenericName;
-import org.opengis.temporal.Duration;
 
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
+import org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
 import org.apache.sis.metadata.iso.content.DefaultCoverageDescription;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
+import org.apache.sis.metadata.iso.identification.DefaultAssociatedResource;
 import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
@@ -186,8 +187,8 @@
 //          Citation.class, "getCollectiveTitle",         "collectiveTitle",         "collectiveTitle",       "Collective title",           InternationalString.class,   -- deprecated as of ISO 19115:2014
             Citation.class, "getISBN",                    "ISBN",                    "ISBN",                  "ISBN",                       String.class,
             Citation.class, "getISSN",                    "ISSN",                    "ISSN",                  "ISSN",                       String.class,
-            Citation.class, "getOnlineResources",         "onlineResources",         "onlineResource",        "Online resources",           OnlineResource[].class,
-            Citation.class, "getGraphics",                "graphics",                "graphic",               "Graphics",                   BrowseGraphic[].class);
+     DefaultCitation.class, "getOnlineResources",         "onlineResources",         "onlineResource",        "Online resources",           OnlineResource[].class,
+     DefaultCitation.class, "getGraphics",                "graphics",                "graphic",               "Graphics",                   BrowseGraphic[].class);
     }
 
     /**
@@ -210,23 +211,23 @@
             Identification.class, "getCredits",                    "credits",                    "credit",                    "Credits",                      String[].class,
             Identification.class, "getStatus",                     "status",                     "status",                    "Status",                       Progress[].class,
             Identification.class, "getPointOfContacts",            "pointOfContacts",            "pointOfContact",            "Point of contacts",            ResponsibleParty[].class,
-            Identification.class, "getSpatialRepresentationTypes", "spatialRepresentationTypes", "spatialRepresentationType", "Spatial representation types", SpatialRepresentationType[].class,
-            Identification.class, "getSpatialResolutions",         "spatialResolutions",         "spatialResolution",         "Spatial resolutions",          Resolution[].class,
-            Identification.class, "getTemporalResolutions",        "temporalResolutions",        "temporalResolution",        "Temporal resolutions",         Duration[].class,
-            Identification.class, "getTopicCategories",            "topicCategories",            "topicCategory",             "Topic categories",             TopicCategory[].class,
-            Identification.class, "getExtents",                    "extents",                    "extent",                    "Extents",                      Extent[].class,
-            Identification.class, "getAdditionalDocumentations",   "additionalDocumentations",   "additionalDocumentation",   "Additional documentations",    Citation[].class,
-            Identification.class, "getProcessingLevel",            "processingLevel",            "processingLevel",           "Processing level",             Identifier.class,
+        DataIdentification.class, "getSpatialRepresentationTypes", "spatialRepresentationTypes", "spatialRepresentationType", "Spatial representation types", SpatialRepresentationType[].class,
+        DataIdentification.class, "getSpatialResolutions",         "spatialResolutions",         "spatialResolution",         "Spatial resolutions",          Resolution[].class,
+//          Identification.class, "getTemporalResolutions",        "temporalResolutions",        "temporalResolution",        "Temporal resolutions",         Duration[].class,
+        DataIdentification.class, "getTopicCategories",            "topicCategories",            "topicCategory",             "Topic categories",             TopicCategory[].class,
+        DataIdentification.class, "getExtents",                    "extents",                    "extent",                    "Extents",                      Extent[].class,
+    AbstractIdentification.class, "getAdditionalDocumentations",   "additionalDocumentations",   "additionalDocumentation",   "Additional documentations",    Citation[].class,
+    AbstractIdentification.class, "getProcessingLevel",            "processingLevel",            "processingLevel",           "Processing level",             Identifier.class,
             Identification.class, "getResourceMaintenances",       "resourceMaintenances",       "resourceMaintenance",       "Resource maintenances",        MaintenanceInformation[].class,
             Identification.class, "getGraphicOverviews",           "graphicOverviews",           "graphicOverview",           "Graphic overviews",            BrowseGraphic[].class,
             Identification.class, "getResourceFormats",            "resourceFormats",            "resourceFormat",            "Resource formats",             Format[].class,
             Identification.class, "getDescriptiveKeywords",        "descriptiveKeywords",        "descriptiveKeywords",       "Descriptive keywords",         Keywords[].class,
             Identification.class, "getResourceSpecificUsages",     "resourceSpecificUsages",     "resourceSpecificUsage",     "Resource specific usages",     Usage[].class,
             Identification.class, "getResourceConstraints",        "resourceConstraints",        "resourceConstraints",       "Resource constraints",         Constraints[].class,
-            Identification.class, "getAssociatedResources",        "associatedResources",        "associatedResource",        "Associated resources",         AssociatedResource[].class,
+    AbstractIdentification.class, "getAssociatedResources",        "associatedResources",        "associatedResource",        "Associated resources",         DefaultAssociatedResource[].class,
         DataIdentification.class, "getEnvironmentDescription",     "environmentDescription",     "environmentDescription",    "Environment description",      InternationalString.class,
         DataIdentification.class, "getSupplementalInformation",    "supplementalInformation",    "supplementalInformation",   "Supplemental information",     InternationalString.class,
-        DataIdentification.class, "getLocalesAndCharsets",         "localesAndCharsets",         "defaultLocale+otherLocale", "Locales and charsets",         Map.class);
+ DefaultDataIdentification.class, "getLocalesAndCharsets",         "localesAndCharsets",         "defaultLocale+otherLocale", "Locales and charsets",         Map.class);
     }
 
     /**
@@ -382,7 +383,7 @@
         /*
          * Compares with the non-deprecated property.
          */
-        final Collection<AttributeGroup> groups = instance.getAttributeGroups();
+        final Collection<DefaultAttributeGroup> groups = instance.getAttributeGroups();
         assertSame(groups, accessor.get(indexOfReplacement, instance));
         assertEquals(CoverageContentType.IMAGE, getSingleton(getSingleton(groups).getContentTypes()));
         /*
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java
index 12ecac1..8c48fa7 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java
@@ -23,7 +23,6 @@
 import java.util.Map;
 import java.lang.reflect.Method;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.collection.CheckedContainer;
@@ -132,11 +131,11 @@
         if (Date.class.isAssignableFrom(type)) {
             return new Date(random.nextInt() * 1000L);
         }
-        if (ControlledVocabulary.class.isAssignableFrom(type)) try {
+        if (CodeList.class.isAssignableFrom(type)) try {
             if (type == CodeList.class) {
                 return null;
             }
-            final ControlledVocabulary[] codes = (ControlledVocabulary[]) type.getMethod("values", (Class[]) null).invoke(null, (Object[]) null);
+            final CodeList<?>[] codes = (CodeList<?>[]) type.getMethod("values", (Class[]) null).invoke(null, (Object[]) null);
             return codes[random.nextInt(codes.length)];
         } catch (ReflectiveOperationException e) {
             fail(e.toString());
@@ -191,7 +190,7 @@
     public void testPropertyValues() {
         random = TestUtilities.createRandomNumberGenerator();
         for (final Class<?> type : types) {
-            if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (!CodeList.class.isAssignableFrom(type)) {
                 final Class<?> impl = getImplementation(type);
                 if (impl != null) {
                     assertTrue("Not an implementation of expected interface.", type.isAssignableFrom(impl));
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeTest.java
index 79db6a9..ad31dae 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeTest.java
@@ -21,7 +21,6 @@
 import org.opengis.metadata.citation.Address;
 import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.Party;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.citation.PresentationForm;
 import org.opengis.metadata.citation.Role;
@@ -190,8 +189,8 @@
     private void assertCitationContentEquals(final int offset, final TableColumn<?> column, final Object... expected) {
         if (valuePolicy == ValueExistencePolicy.COMPACT) {
             assertEquals(19, expected.length);
-            System.arraycopy(expected, 11+offset, expected, 10+offset,  8-offset);    // Compact the "Individual" element.
-            System.arraycopy(expected,  7+offset, expected,  6+offset, 11-offset);    // Compact the "Organisation" element.
+            System.arraycopy(expected, 12+offset, expected, 11+offset,  7-offset);    // Compact the "Individual" element.
+            System.arraycopy(expected,  8+offset, expected,  7+offset, 10-offset);    // Compact the "Organisation" element.
             System.arraycopy(expected,  1+offset, expected,    offset, 16-offset);    // Compact the "Title" element.
             Arrays.fill(expected, 16, 19, null);
         }
@@ -211,16 +210,16 @@
               "Alternate title (2 of 2)",
               "Edition",
               "Cited responsible party (1 of 2)",
-                "Organisation",
-                  "Name",                               // In COMPACT mode, this value is associated to "Organisation" node.
                 "Role",
+                "Party",
+                  "Name",                               // In COMPACT mode, this value is associated to "Organisation" node.
               "Cited responsible party (2 of 2)",
-                "Individual",
+                "Role",
+                "Party",
                   "Name",                               // In COMPACT mode, this value is associated to "Individual" node.
                   "Contact info",
                     "Address",
                       "Electronic mail address",
-                "Role",
               "Presentation form (1 of 2)",
               "Presentation form (2 of 2)",
               "Other citation details");
@@ -242,16 +241,16 @@
               "alternateTitle",
               "edition",
               "citedResponsibleParty",
+                "role",
                 "party",
                   "name",                               // In COMPACT mode, this value is associated to "party" node.
-                "role",
               "citedResponsibleParty",
+                "role",
                 "party",
                   "name",                               // In COMPACT mode, this value is associated to "party" node.
                   "contactInfo",
                     "address",
                       "electronicMailAddress",
-                "role",
               "presentationForm",
               "presentationForm",
               "otherCitationDetails");
@@ -273,16 +272,16 @@
               ONE,          // alternateTitle
               null,         // edition
               ZERO,         // citedResponsibleParty
+                null,       // role
                 ZERO,       // party (organisation)
                   null,     // name                         — in COMPACT mode, this value is associated to "party" node.
-                null,       // role
               ONE,          // citedResponsibleParty
+                null,       // role
                 ZERO,       // party (individual)
                   null,     // name                         — in COMPACT mode, this value is associated to "party" node.
                   ZERO,     // contactInfo
                     ZERO,   // address
                       ZERO, // electronicMailAddress
-                null,       // role
               ZERO,         // presentationForm
               ONE,          // presentationForm
               null);        // otherCitationDetails
@@ -301,16 +300,16 @@
               InternationalString.class,
               InternationalString.class,
               ResponsibleParty.class,
-                Party.class,                            // In COMPACT mode, value with be the one of "name" node instead.
-                  InternationalString.class,            // Name
                 Role.class,
+                AbstractParty.class,                    // In COMPACT mode, value with be the one of "name" node instead.
+                  InternationalString.class,            // Name
               ResponsibleParty.class,
-                Party.class,                            // In COMPACT mode, value with be the one of "name" node instead.
+                Role.class,
+                AbstractParty.class,                    // In COMPACT mode, value with be the one of "name" node instead.
                   InternationalString.class,            // Name
                   Contact.class,
                     Address.class,
                       String.class,
-                Role.class,
               PresentationForm.class,
               PresentationForm.class,
               InternationalString.class);
@@ -329,16 +328,16 @@
               "Second alternate title",
               "Some edition",
               null,                             // ResponsibleParty
+                Role.DISTRIBUTOR,
                 null,                           // Party (organisation)
                   "Some organisation",
-                Role.DISTRIBUTOR,
               null,                             // ResponsibleParty
+                Role.POINT_OF_CONTACT,
                 null,                           // Party (individual)
                   "Some person of contact",
                   null,                         // Contact
                     null,                       // Address
                       "Some email",
-                Role.POINT_OF_CONTACT,
               PresentationForm.MAP_DIGITAL,
               PresentationForm.MAP_HARDCOPY,
               "Some other details");
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
index 9571ad9..67c4165 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
@@ -87,9 +87,10 @@
             "Citation……………………………………………………………………………… Undercurrent\n" +
             "  ├─Alternate title………………………………………………… Andākarento\n" +
             "  ├─Cited responsible party (1 of 2)\n" +
-            "  │   ├─Individual…………………………………………………… Testsuya Toyoda\n" +
-            "  │   └─Role…………………………………………………………………… Author\n" +
+            "  │   ├─Role…………………………………………………………………… Author\n" +
+            "  │   └─Party………………………………………………………………… Testsuya Toyoda\n" +
             "  ├─Cited responsible party (2 of 2)\n" +
+            "  │   ├─Role…………………………………………………………………… EDITOR\n" +
             "  │   ├─Extent……………………………………………………………… World\n" +
             "  │   │   └─Geographic element\n" +
             "  │   │       ├─West bound longitude…… 180°W\n" +
@@ -97,8 +98,7 @@
             "  │   │       ├─South bound latitude…… 90°S\n" +
             "  │   │       ├─North bound latitude…… 90°N\n" +
             "  │   │       └─Extent type code……………… True\n" +
-            "  │   ├─Organisation……………………………………………… Kōdansha\n" +
-            "  │   └─Role…………………………………………………………………… Editor\n" +
+            "  │   └─Party………………………………………………………………… Kōdansha\n" +
             "  ├─Presentation form (1 of 2)…………………… Document digital\n" +
             "  ├─Presentation form (2 of 2)…………………… Document hardcopy\n" +
             "  └─ISBN……………………………………………………………………………… 9782505004509\n", text);
@@ -177,7 +177,7 @@
             "  ├─Alternate title (2 of 3)………… Orange\n" +
             "  ├─Alternate title (3 of 3)………… Kiwi\n" +
             "  ├─Presentation form (1 of 3)…… Image digital\n" +
-            "  ├─Presentation form (2 of 3)…… Audio digital\n" +
+            "  ├─Presentation form (2 of 3)…… AUDIO-DIGITAL\n" + // Missing localization resource for that one.
             "  └─Presentation form (3 of 3)…… Test\n",
             text);
     }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java
index 388d812..d353cba 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java
@@ -60,14 +60,14 @@
             "  ├─Alternate title (2 of 2)…………………………………………… Second alternate title\n" +
             "  ├─Edition………………………………………………………………………………………… Some edition\n" +
             "  ├─Cited responsible party (1 of 2)\n" +
-            "  │   ├─Organisation………………………………………………………………… Some organisation\n" +
-            "  │   └─Role……………………………………………………………………………………… Distributor\n" +
+            "  │   ├─Role……………………………………………………………………………………… Distributor\n" +
+            "  │   └─Party…………………………………………………………………………………… Some organisation\n" +
             "  ├─Cited responsible party (2 of 2)\n" +
-            "  │   ├─Individual……………………………………………………………………… Some person of contact\n" +
-            "  │   │   └─Contact info\n" +
-            "  │   │       └─Address\n" +
-            "  │   │           └─Electronic mail address…… Some email\n" +
-            "  │   └─Role……………………………………………………………………………………… Point of contact\n" +
+            "  │   ├─Role……………………………………………………………………………………… Point of contact\n" +
+            "  │   └─Party…………………………………………………………………………………… Some person of contact\n" +
+            "  │       └─Contact info\n" +
+            "  │           └─Address\n" +
+            "  │               └─Electronic mail address…… Some email\n" +
             "  ├─Presentation form (1 of 2)……………………………………… Map digital\n" +
             "  ├─Presentation form (2 of 2)……………………………………… Map hardcopy\n" +
             "  └─Other citation details………………………………………………… Some other details\n";
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
index 464cdf2a..99dbd13 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
@@ -18,7 +18,7 @@
 
 import java.lang.reflect.Modifier;
 import org.opengis.annotation.UML;
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.metadata.MetadataStandard;
 import org.apache.sis.metadata.PropertyConsistencyCheck;
@@ -51,14 +51,12 @@
     @SuppressWarnings("deprecation")
     public AllMetadataTest() {
         super(MetadataStandard.ISO_19115,
-            org.opengis.annotation.Obligation.class,
             org.opengis.metadata.ApplicationSchemaInformation.class,
             org.opengis.metadata.Datatype.class,
             org.opengis.metadata.ExtendedElementInformation.class,
             org.opengis.metadata.Identifier.class,
             org.opengis.metadata.Metadata.class,
             org.opengis.metadata.MetadataExtensionInformation.class,
-            org.opengis.metadata.MetadataScope.class,
             org.opengis.metadata.PortrayalCatalogueReference.class,
             org.opengis.metadata.acquisition.AcquisitionInformation.class,
             org.opengis.metadata.acquisition.Context.class,
@@ -85,9 +83,7 @@
             org.opengis.metadata.citation.DateType.class,
             org.opengis.metadata.citation.OnLineFunction.class,
             org.opengis.metadata.citation.OnlineResource.class,
-            org.opengis.metadata.citation.Party.class,
             org.opengis.metadata.citation.PresentationForm.class,
-            org.opengis.metadata.citation.Responsibility.class,
             org.opengis.metadata.citation.ResponsibleParty.class,
             org.opengis.metadata.citation.Role.class,
             org.opengis.metadata.citation.Series.class,
@@ -97,7 +93,6 @@
             org.opengis.metadata.constraint.LegalConstraints.class,
             org.opengis.metadata.constraint.Restriction.class,
             org.opengis.metadata.constraint.SecurityConstraints.class,
-            org.opengis.metadata.content.AttributeGroup.class,
             org.opengis.metadata.content.Band.class,
             org.opengis.metadata.content.BandDefinition.class,
             org.opengis.metadata.content.ContentInformation.class,
@@ -109,7 +104,6 @@
             org.opengis.metadata.content.PolarizationOrientation.class,
             org.opengis.metadata.content.RangeDimension.class,
             org.opengis.metadata.content.RangeElementDescription.class,
-            org.opengis.metadata.content.SampleDimension.class,
             org.opengis.metadata.content.TransferFunctionType.class,
             org.opengis.metadata.distribution.DataFile.class,
             org.opengis.metadata.distribution.DigitalTransferOptions.class,
@@ -129,20 +123,15 @@
             org.opengis.metadata.extent.TemporalExtent.class,
             org.opengis.metadata.extent.VerticalExtent.class,
             org.opengis.metadata.identification.AggregateInformation.class,
-            org.opengis.metadata.identification.AssociatedResource.class,
             org.opengis.metadata.identification.AssociationType.class,
             org.opengis.metadata.identification.BrowseGraphic.class,
             org.opengis.metadata.identification.CharacterSet.class,
-            org.opengis.metadata.identification.CoupledResource.class,
             org.opengis.metadata.identification.DataIdentification.class,
             org.opengis.metadata.identification.Identification.class,
             org.opengis.metadata.identification.InitiativeType.class,
             org.opengis.metadata.identification.Keywords.class,
-            org.opengis.metadata.identification.KeywordClass.class,
             org.opengis.metadata.identification.KeywordType.class,
             org.opengis.metadata.identification.Progress.class,
-            org.opengis.metadata.identification.OperationChainMetadata.class,
-            org.opengis.metadata.identification.OperationMetadata.class,
             org.opengis.metadata.identification.RepresentativeFraction.class,
             org.opengis.metadata.identification.Resolution.class,
             org.opengis.metadata.identification.ServiceIdentification.class,
@@ -157,7 +146,6 @@
             org.opengis.metadata.lineage.Source.class,
             org.opengis.metadata.maintenance.MaintenanceFrequency.class,
             org.opengis.metadata.maintenance.MaintenanceInformation.class,
-            org.opengis.metadata.maintenance.Scope.class,
             org.opengis.metadata.maintenance.ScopeCode.class,
             org.opengis.metadata.maintenance.ScopeDescription.class,
             org.opengis.metadata.quality.AbsoluteExternalPositionalAccuracy.class,
@@ -239,9 +227,13 @@
              */
             return null;
         }
+        String identifier = type.getAnnotation(UML.class).identifier();
+        if (identifier.equals("MI_PolarizationOrientationCode")) {
+            identifier = "MI_PolarisationOrientationCode";
+            // https://issues.apache.org/jira/browse/SIS-398
+        }
         final String classname = "org.apache.sis.internal.jaxb." +
-                (ControlledVocabulary.class.isAssignableFrom(type) ? "code" : "metadata") +
-                '.' + type.getAnnotation(UML.class).identifier();
+                (CodeList.class.isAssignableFrom(type) ? "code" : "metadata") + '.' + identifier;
         final Class<?> wrapper = Class.forName(classname);
         Class<?>[] expectedFinalClasses = wrapper.getClasses();   // "Since2014" internal class.
         if (expectedFinalClasses.length == 0) {
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java
index c01907e..87186e4 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java
@@ -27,7 +27,12 @@
 import org.opengis.util.NameFactory;
 import org.opengis.metadata.identification.*;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.opengis.metadata.constraint.Constraints;
+import org.opengis.metadata.distribution.Format;
 import org.opengis.metadata.extent.Extent;
+import org.opengis.metadata.maintenance.MaintenanceInformation;
+import org.opengis.metadata.spatial.SpatialRepresentationType;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
@@ -105,9 +110,25 @@
                 return factory.createInternationalString(names);
             }
 
-            @Override public Citation                  getCitation()           {return null;}
-            @Override public Collection<TopicCategory> getTopicCategories()    {return null;}
-            @Override public Collection<Extent>        getExtents()            {return null;}
+            @Override public InternationalString                   getSupplementalInformation()    {return null;}
+            @Override public Citation                              getCitation()                   {return null;}
+            @Override public InternationalString                   getPurpose()                    {return null;}
+            @Override public Collection<SpatialRepresentationType> getSpatialRepresentationTypes() {return null;}
+            @Override public Collection<Resolution>                getSpatialResolutions()         {return null;}
+            @Override public Collection<Locale>                    getLanguages()                  {return null;}
+            @Override public Collection<CharacterSet>              getCharacterSets()              {return null;}
+            @Override public Collection<TopicCategory>             getTopicCategories()            {return null;}
+            @Override public Collection<Extent>                    getExtents()                    {return null;}
+            @Override public Collection<String>                    getCredits()                    {return null;}
+            @Override public Collection<Progress>                  getStatus()                     {return null;}
+            @Override public Collection<ResponsibleParty>          getPointOfContacts()            {return null;}
+            @Override public Collection<MaintenanceInformation>    getResourceMaintenances()       {return null;}
+            @Override public Collection<BrowseGraphic>             getGraphicOverviews()           {return null;}
+            @Override public Collection<Format>                    getResourceFormats()            {return null;}
+            @Override public Collection<Keywords>                  getDescriptiveKeywords()        {return null;}
+            @Override public Collection<Usage>                     getResourceSpecificUsages()     {return null;}
+            @Override public Collection<Constraints>               getResourceConstraints()        {return null;}
+@Deprecated @Override public Collection<AggregateInformation>      getAggregationInfo()            {return null;}
         };
         final DefaultMetadata data = new DefaultMetadata();
         data.setIdentificationInfo(singleton(identification));
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
index 214ef0f..3c40cea 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
@@ -23,7 +23,6 @@
 import java.util.Collection;
 import java.net.URISyntaxException;
 import javax.xml.bind.JAXBException;
-import org.opengis.metadata.MetadataScope;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.DateType;
 import org.opengis.metadata.maintenance.ScopeCode;
@@ -183,9 +182,9 @@
         /*
          * The above deprecated methods shall have created MetadataScope object. Verify that.
          */
-        final Collection<MetadataScope> scopes = metadata.getMetadataScopes();
-        final Iterator<MetadataScope> it = scopes.iterator();
-        MetadataScope scope = it.next();
+        final Collection<DefaultMetadataScope> scopes = metadata.getMetadataScopes();
+        final Iterator<DefaultMetadataScope> it = scopes.iterator();
+        DefaultMetadataScope scope = it.next();
         assertEquals("metadataScopes[0].name", "Bridges", scope.getName().toString());
         assertEquals("metadataScopes[0].resourceScope", ScopeCode.FEATURE_TYPE, scope.getResourceScope());
         scope = it.next();
@@ -229,7 +228,7 @@
          */
         Date creation = date("2014-10-07 00:00:00");
         final DefaultCitationDate[] dates = new DefaultCitationDate[] {
-                new DefaultCitationDate(date("2014-10-09 00:00:00"), DateType.LAST_UPDATE),
+                new DefaultCitationDate(date("2014-10-09 00:00:00"), DateType.valueOf("LAST_UPDATE")),
                 new DefaultCitationDate(creation, DateType.CREATION)
         };
         metadata.setDateInfo(Arrays.asList(dates));
@@ -278,7 +277,5 @@
         final DefaultMetadata metadata = new DefaultMetadata();
         metadata.setDataSetUri("file:/tmp/myfile.txt");
         assertEquals("file:/tmp/myfile.txt", metadata.getDataSetUri());
-        assertEquals("file:/tmp/myfile.txt", getSingleton(getSingleton(metadata.getIdentificationInfo())
-                .getCitation().getOnlineResources()).getLinkage().toString());
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java
index aa1b2df..513de3d 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java
@@ -26,6 +26,7 @@
 import java.lang.reflect.Field;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.internal.simple.CitationConstant;
 import org.apache.sis.internal.simple.SimpleCitation;
@@ -120,21 +121,21 @@
      */
     @Test
     public void testGetIdentifier() {
-        assertEquals("Apache:SIS",           getIdentifier(SIS));
+        assertEquals("SIS",                  getIdentifier(SIS));
         assertEquals("OGC",                  getIdentifier(OGC));
         assertEquals("IOGP",                 getIdentifier(IOGP));
         assertEquals("EPSG",                 getIdentifier(EPSG));
-        assertEquals("ESRI:ArcGIS",          getIdentifier(ESRI));
+        assertEquals("ArcGIS",               getIdentifier(ESRI));
         assertEquals("NetCDF",               getIdentifier(NETCDF));
         assertEquals("GeoTIFF",              getIdentifier(GEOTIFF));
-        assertEquals("Pitney Bowes:MapInfo", getIdentifier(MAP_INFO));
+        assertEquals("MapInfo",              getIdentifier(MAP_INFO));
         assertEquals("ISBN",                 getIdentifier(ISBN));
         assertEquals("ISSN",                 getIdentifier(ISSN));
-        assertEquals("OSGeo:PROJ",           getIdentifier(PROJ4));              // Not a valid Unicode identifier.
-        assertEquals("IHO:S-57",             getIdentifier(S57));                // Not a valid Unicode identifier.
-        assertEquals("ISO:19115-1",          getIdentifier(ISO_19115.get(0)));   // The ':' separator is not usual in ISO references
-        assertEquals("ISO:19115-2",          getIdentifier(ISO_19115.get(1)));   // and could be changed in future SIS versions.
-        assertEquals("OGC:WMS",              getIdentifier(WMS));
+        assertEquals("PROJ",                 getIdentifier(PROJ4));
+        assertEquals("S-57",                 getIdentifier(S57));                // Not a valid Unicode identifier.
+        assertEquals("19115-1",              getIdentifier(ISO_19115.get(0)));   // The ':' separator is not usual in ISO references
+        assertEquals("19115-2",              getIdentifier(ISO_19115.get(1)));   // and could be changed in future SIS versions.
+        assertEquals("WMS",                  getIdentifier(WMS));
     }
 
     /**
@@ -220,6 +221,7 @@
      * Tests {@code getCitedResponsibleParties()} on some {@code Citation} constants.
      */
     @Test
+    @org.junit.Ignore("Requires GeoAPI 3.1.")
     public void testGetCitedResponsibleParty() {
         assertEquals("Open Geospatial Consortium",                       getCitedResponsibleParty(OGC));
         assertEquals("International Organization for Standardization",   getCitedResponsibleParty(ISO_19115.get(0)));
@@ -232,7 +234,7 @@
      * Returns the responsible party for the given constant.
      */
     private static String getCitedResponsibleParty(final Citation citation) {
-        return getSingleton(getSingleton(citation.getCitedResponsibleParties()).getParties()).getName().toString(Locale.US);
+        return getSingleton(citation.getCitedResponsibleParties()).getOrganisationName().toString(Locale.US);
     }
 
     /**
@@ -246,7 +248,7 @@
     public void testEPSG() {
         final Identifier identifier = getSingleton(EPSG.getIdentifiers());
         assertEquals("EPSG", toCodeSpace(EPSG));
-        assertEquals("IOGP", identifier.getCodeSpace());
+//      assertEquals("IOGP", identifier.getCodeSpace());
         assertEquals("EPSG", identifier.getCode());
     }
 
@@ -286,17 +288,24 @@
      */
     @Test
     public void testIdentifierMatches() {
-        final Identifier ogc = new DefaultIdentifier("OGC", "06-042", null);
-        final Identifier iso = new DefaultIdentifier("ISO", "19128", null);
+        final Identifier ogc = new Id("OGC", "06-042");
+        final Identifier iso = new Id("ISO", "19128");
         final DefaultCitation citation = new DefaultCitation("Web Map Server");
         citation.setIdentifiers(Arrays.asList(ogc, iso, new DefaultIdentifier("Foo", "06-042", null)));
         assertTrue ("With full identifier",  Citations.identifierMatches(citation, ogc, ogc.getCode()));
         assertTrue ("With full identifier",  Citations.identifierMatches(citation, iso, iso.getCode()));
-        assertFalse("With wrong code",       Citations.identifierMatches(citation, new DefaultIdentifier("ISO", "19115", null), "19115"));
-        assertFalse("With wrong code space", Citations.identifierMatches(citation, new DefaultIdentifier("Foo", "19128", null), "19128"));
+        assertFalse("With wrong code",       Citations.identifierMatches(citation, new Id("ISO", "19115"), "19115"));
+        assertFalse("With wrong code space", Citations.identifierMatches(citation, new Id("Foo", "19128"), "19128"));
         assertFalse("With wrong code",       Citations.identifierMatches(citation, "Foo"));
         assertTrue ("Without identifier",    Citations.identifierMatches(citation, "19128"));
         assertTrue ("With parsing",          Citations.identifierMatches(citation, "ISO:19128"));
         assertFalse("With wrong code space", Citations.identifierMatches(citation, "Foo:19128"));
     }
+
+    @SuppressWarnings("serial")
+    private static final class Id extends DefaultIdentifier implements ReferenceIdentifier {
+        Id(String codeSpace, String code) {
+            super(codeSpace, code, null);
+        }
+    }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
index 81be7ca..b3ec5b1 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
@@ -27,9 +27,7 @@
 import org.opengis.metadata.citation.CitationDate;
 import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.citation.DateType;
-import org.opengis.metadata.citation.Party;
 import org.opengis.metadata.citation.Role;
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.citation.OnLineFunction;
 import org.opengis.metadata.citation.OnlineResource;
@@ -92,7 +90,7 @@
         final DefaultResponsibleParty author = new DefaultResponsibleParty(Role.AUTHOR);
         author.setParties(Collections.singleton(new DefaultIndividual("Testsuya Toyoda", null, null)));
 
-        final DefaultResponsibleParty editor = new DefaultResponsibleParty(Role.EDITOR);
+        final DefaultResponsibleParty editor = new DefaultResponsibleParty(Role.valueOf("EDITOR"));
         editor.setParties(Collections.singleton(new DefaultOrganisation("Kōdansha", null, null, null)));
         editor.setExtents(Collections.singleton(Extents.WORLD));
 
@@ -189,12 +187,12 @@
         /*
          * Verify the author metadata.
          */
-        final Responsibility re = CollectionsExt.first(original.getCitedResponsibleParties());
-        final Responsibility ra = CollectionsExt.first(clone   .getCitedResponsibleParties());
+        final ResponsibleParty re = CollectionsExt.first(original.getCitedResponsibleParties());
+        final ResponsibleParty ra = CollectionsExt.first(clone   .getCitedResponsibleParties());
         assertNotSame("citedResponsibleParty", re, ra);
         assertSame("role", re.getRole(), ra.getRole());
-        assertSame("name", getSingleton(re.getParties()).getName(),
-                           getSingleton(ra.getParties()).getName());
+        assertSame("name", re.getIndividualName(),
+                           ra.getIndividualName());
     }
 
     /**
@@ -255,12 +253,12 @@
         contact.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "ip-protocol");
         final DefaultCitation c = new DefaultCitation("Fight against poverty");
         final DefaultResponsibleParty r1 = new DefaultResponsibleParty(Role.ORIGINATOR);
-        final DefaultResponsibleParty r2 = new DefaultResponsibleParty(Role.FUNDER);
+        final DefaultResponsibleParty r2 = new DefaultResponsibleParty(Role.valueOf("funder"));
         r1.setParties(Collections.singleton(new DefaultIndividual("Maid Marian", null, contact)));
         r2.setParties(Collections.singleton(new DefaultIndividual("Robin Hood",  null, contact)));
         c.setCitedResponsibleParties(Arrays.asList(r1, r2));
-        c.getDates().add(new DefaultCitationDate(TestUtilities.date("2015-10-17 00:00:00"), DateType.ADOPTED));
-        c.getPresentationForms().add(PresentationForm.PHYSICAL_OBJECT);
+        c.getDates().add(new DefaultCitationDate(TestUtilities.date("2015-10-17 00:00:00"), DateType.valueOf("adopted")));
+        c.getPresentationForms().add(PresentationForm.valueOf("physicalObject"));
         /*
          * Check that XML file built by the marshaller is the same as the example file.
          */
@@ -306,31 +304,31 @@
 
         final CitationDate date = getSingleton(c.getDates());
         assertEquals("date", date.getDate(), TestUtilities.date("2015-10-17 00:00:00"));
-        assertEquals("dateType", DateType.ADOPTED, date.getDateType());
-        assertEquals("presentationForm", PresentationForm.PHYSICAL_OBJECT, getSingleton(c.getPresentationForms()));
+        assertEquals("dateType", DateType.valueOf("adopted"), date.getDateType());
+        assertEquals("presentationForm", PresentationForm.valueOf("physicalObject"), getSingleton(c.getPresentationForms()));
 
         final Iterator<ResponsibleParty> it = c.getCitedResponsibleParties().iterator();
         final Contact contact = assertResponsibilityEquals(Role.ORIGINATOR, "Maid Marian", it.next());
         assertEquals("Contact instruction", "Send carrier pigeon.", String.valueOf(contact.getContactInstructions()));
 
-        final OnlineResource resource = TestUtilities.getSingleton(contact.getOnlineResources());
+        final OnlineResource resource = contact.getOnlineResource();
         assertEquals("Resource name", "IP over Avian Carriers", String.valueOf(resource.getName()));
         assertEquals("Resource description", "High delay, low throughput, and low altitude service.", String.valueOf(resource.getDescription()));
         assertEquals("Resource linkage", "https://tools.ietf.org/html/rfc1149", String.valueOf(resource.getLinkage()));
         assertEquals("Resource function", OnLineFunction.OFFLINE_ACCESS, resource.getFunction());
 
         // Thanks to xlink:href, the Contact shall be the same instance as above.
-        assertSame("contact", contact, assertResponsibilityEquals(Role.FUNDER, "Robin Hood", it.next()));
+        assertSame("contact", contact, assertResponsibilityEquals(Role.valueOf("funder"), "Robin Hood", it.next()));
         assertFalse(it.hasNext());
     }
 
     /**
      * Asserts that the given responsibility has the expected properties, then returns its contact info.
      */
-    private static Contact assertResponsibilityEquals(final Role role, final String name, final Responsibility actual) {
+    private static Contact assertResponsibilityEquals(final Role role, final String name, final ResponsibleParty actual) {
         assertEquals("role", role, actual.getRole());
-        final Party p = getSingleton(actual.getParties());
-        assertEquals("name", name, String.valueOf(p.getName()));
+        final AbstractParty p = getSingleton(((DefaultResponsibleParty) actual).getParties());
+        assertEquals("name", name, p.getName().toString());
         return getSingleton(p.getContactInfo());
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
index e4c0dbe..0261fae 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
@@ -21,8 +21,8 @@
 import java.util.logging.Filter;
 import java.util.logging.LogRecord;
 import org.opengis.metadata.citation.Telephone;
-import org.opengis.metadata.citation.TelephoneType;
 import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.xml.TestCase;
 import org.junit.Test;
@@ -82,10 +82,10 @@
     @SuppressWarnings("deprecation")
     public void testSetPhones() {
         init();
-        final DefaultTelephone   tel1 = new DefaultTelephone("00.01", TelephoneType.SMS);
-        final DefaultTelephone   tel2 = new DefaultTelephone("00.02", TelephoneType.VOICE);
-        final DefaultTelephone   tel3 = new DefaultTelephone("00.03", TelephoneType.FACSIMILE);
-        final DefaultTelephone   tel4 = new DefaultTelephone("00.04", TelephoneType.VOICE);
+        final DefaultTelephone   tel1 = new DefaultTelephone("00.01", UnsupportedCodeList.valueOf("SMS"));
+        final DefaultTelephone   tel2 = new DefaultTelephone("00.02", UnsupportedCodeList.VOICE);
+        final DefaultTelephone   tel3 = new DefaultTelephone("00.03", UnsupportedCodeList.FACSIMILE);
+        final DefaultTelephone   tel4 = new DefaultTelephone("00.04", UnsupportedCodeList.VOICE);
         final DefaultTelephone[] tels = new DefaultTelephone[] {tel1, tel2, tel3, tel4};
         final DefaultContact  contact = new DefaultContact();
         contact.setPhones(Arrays.asList(tel1, tel2, tel3, tel4));
@@ -96,7 +96,7 @@
          */
         assertSame("getPhone", tel2, contact.getPhone()); // Shall ignore the TelephoneType.SMS.
         assertEquals("warningOccured", "IgnoredPropertyAssociatedTo_1", resourceKey);
-        assertArrayEquals("warningOccured", new String[] {"TelephoneType[SMS]"}, parameters);
+        assertArrayEquals("warningOccured", new String[] {"SMS"}, parameters);
         verifyLegacyLists(tels);
     }
 
@@ -154,8 +154,6 @@
         final Telephone view;
         if (hideSIS) {
             view = new Telephone() {
-                @Override public String             getNumber()     {return tel.getNumber();}
-                @Override public TelephoneType      getNumberType() {return tel.getNumberType();}
                 @Override public Collection<String> getVoices()     {return tel.getVoices();}
                 @Override public Collection<String> getFacsimiles() {return tel.getFacsimiles();}
             };
@@ -170,9 +168,9 @@
         contact.setPhone(view);
         verifyLegacyLists(view);
         assertArrayEquals("getPhones", new DefaultTelephone[] {
-                new DefaultTelephone("00.02", TelephoneType.VOICE),
-                new DefaultTelephone("00.04", TelephoneType.VOICE),
-                new DefaultTelephone("00.03", TelephoneType.FACSIMILE)
+                new DefaultTelephone("00.02", UnsupportedCodeList.VOICE),
+                new DefaultTelephone("00.04", UnsupportedCodeList.VOICE),
+                new DefaultTelephone("00.03", UnsupportedCodeList.FACSIMILE)
             }, contact.getPhones().toArray());
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java
index c1e7e2e..0f6deec 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java
@@ -57,7 +57,7 @@
                 "        <gco:CharacterString>An author</gco:CharacterString>\n" +
                 "      </gmd:individualName>\n" +
                 "      <gmd:role>\n" +
-                "        <gmd:CI_RoleCode codeList=\"http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#CI_RoleCode\" codeListValue=\"author\" codeSpace=\"eng\">Author</gmd:CI_RoleCode>\n" +
+                "        <gmd:CI_RoleCode codeList=\"http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#CI_RoleCode\" codeListValue=\"author\">Author</gmd:CI_RoleCode>\n" +
                 "      </gmd:role>\n" +
                 "    </gmd:CI_ResponsibleParty>\n" +
                 "  </gmd:citedResponsibleParty>\n" +
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java
index 6708b61..b8a8635 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java
@@ -65,7 +65,7 @@
     }
 
     /**
-     * Tests (un)marshalling of a XML fragment containing the {@link Restriction#LICENCE} code.
+     * Tests (un)marshalling of a XML fragment containing the {@link Restriction#LICENSE} code.
      * The spelling changed between ISO 19115:2003 and 19115:2014, from "license" to "licence".
      * We need to ensure that XML marshalling use the old spelling, until the XML schema is updated.
      *
@@ -78,16 +78,15 @@
                 "  <mco:useConstraints>\n" +
                 "    <mco:MD_RestrictionCode"
                         + " codeList=\"http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#MD_RestrictionCode\""
-                        + " codeListValue=\"licence\""
-                        + " codeSpace=\"eng\">Licence</mco:MD_RestrictionCode>\n" +
+                        + " codeListValue=\"licence\">License</mco:MD_RestrictionCode>\n" +
                 "  </mco:useConstraints>\n" +
                 "</mco:MD_LegalConstraints>\n";
 
         final DefaultLegalConstraints c = new DefaultLegalConstraints();
-        c.setUseConstraints(singleton(Restriction.LICENCE));
+        c.setUseConstraints(singleton(Restriction.LICENSE));
         assertXmlEquals(xml, marshal(c), "xmlns:*");
         DefaultLegalConstraints actual = unmarshal(DefaultLegalConstraints.class, xml);
-        assertSame(Restriction.LICENCE, getSingleton(actual.getUseConstraints()));
+        assertSame(Restriction.LICENSE, getSingleton(actual.getUseConstraints()));
         assertEquals(c, actual);
         /*
          * Above code tested ISO 19115-3 (un)marshalling. Code below test legacy ISO 19139:2007 (un)marshalling.
@@ -98,14 +97,13 @@
                 "  <gmd:useConstraints>\n" +
                 "    <gmd:MD_RestrictionCode"
                         + " codeList=\"http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_RestrictionCode\""
-                        + " codeListValue=\"license\""                              // Note the "s" - from old ISO 19115:2013 spelling.
-                        + " codeSpace=\"eng\">Licence</gmd:MD_RestrictionCode>\n" + // Note the "c" - this one come from resource file.
+                        + " codeListValue=\"license\">License</gmd:MD_RestrictionCode>\n" +
                 "  </gmd:useConstraints>\n" +
                 "</gmd:MD_LegalConstraints>\n";
 
         assertXmlEquals(xml, marshal(c, VERSION_2007), "xmlns:*");
         actual = unmarshal(DefaultLegalConstraints.class, xml);
-        assertSame(Restriction.LICENCE, getSingleton(actual.getUseConstraints()));
+        assertSame(Restriction.LICENSE, getSingleton(actual.getUseConstraints()));
         assertEquals(c, actual);
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/content/DefaultBandTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/content/DefaultBandTest.java
index 84a76f0..5bd0cf1 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/content/DefaultBandTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/content/DefaultBandTest.java
@@ -47,7 +47,7 @@
             + "  </mrc:nominalSpatialResolution>\n"
             + "  <mrc:detectedPolarisation>\n"
             + "    <mrc:MI_PolarisationOrientationCode "    // Spell with "s" in 2014 schema.
-            +       "codeList=\"http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#MI_PolarisationOrientationCode\" codeListValue=\"vertical\">"
+            +       "codeList=\"http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#MI_PolarizationOrientationCode\" codeListValue=\"vertical\">"
             +       "Vertical"
             +     "</mrc:MI_PolarisationOrientationCode>\n"
             + "  </mrc:detectedPolarisation>\n"
@@ -65,7 +65,7 @@
             + "  </gmi:nominalSpatialResolution>\n"
             + "  <gmi:detectedPolarisation>\n"
             + "    <gmi:MI_PolarizationOrientationCode "    // Spell with "z" in 2003 schema.
-            +       "codeList=\"http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MI_PolarisationOrientationCode\" codeListValue=\"vertical\">" +
+            +       "codeList=\"http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MI_PolarizationOrientationCode\" codeListValue=\"vertical\">" +
                     "Vertical"
             +     "</gmi:MI_PolarizationOrientationCode>\n"
             + "  </gmi:detectedPolarisation>\n"
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java
index 638402f..6ebf23e 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java
@@ -20,8 +20,7 @@
 import org.opengis.util.NameFactory;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.identification.OperationMetadata;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
 import org.apache.sis.internal.jaxb.metadata.replace.ServiceParameterTest;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.xml.NilReason;
@@ -47,8 +46,9 @@
      * Creates the resource to use for testing purpose.
      */
     static DefaultCoupledResource create(final NameFactory factory) {
-        final DefaultOperationMetadata operation = new DefaultOperationMetadata("Get Map",
-                DistributedComputingPlatform.WEB_SERVICES, null);
+        final DefaultOperationMetadata operation = new DefaultOperationMetadata();
+        operation.setOperationName("Get Map");
+        operation.setDistributedComputingPlatforms(singleton(UnsupportedCodeList.valueOf("WEB_SERVICES")));
         operation.setParameters(singleton((ParameterDescriptor<?>) ServiceParameterTest.create()));
         operation.setConnectPoints(singleton(NilReason.MISSING.createNilObject(OnlineResource.class)));
 
@@ -64,7 +64,7 @@
     @Test
     public void testOperationNameResolve() {
         final DefaultCoupledResource resource  = create(DefaultFactories.forBuildin(NameFactory.class));
-        final OperationMetadata      operation = resource.getOperation();
+        final DefaultOperationMetadata operation = resource.getOperation();
         /*
          * Test OperationName replacement when the name matches.
          */
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java
index 9ee0684..b0c6b8c 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java
@@ -18,14 +18,9 @@
 
 import javax.xml.bind.JAXBException;
 import org.opengis.util.NameFactory;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.identification.CouplingType;
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.OperationMetadata;
-import org.opengis.metadata.identification.ServiceIdentification;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.metadata.xml.TestUsingFile;
@@ -70,7 +65,7 @@
                 "A dummy service for testing purpose.");                // abstract
         id.setServiceTypeVersions(singleton("1.0"));
         id.setCoupledResources(singleton(resource));
-        id.setCouplingType(CouplingType.LOOSE);
+        id.setCouplingType(UnsupportedCodeList.valueOf("LOOSE"));
         id.setContainsOperations(singleton(resource.getOperation()));
         return id;
     }
@@ -79,28 +74,28 @@
      * Compare values of the given service identifications against the value expected for the
      * instance created by {@link #create()} method.
      */
-    private static void verify(final ServiceIdentification id) {
+    private static void verify(final DefaultServiceIdentification id) {
         assertEquals("serviceTypeVersion", "1.0",                                  getSingleton(id.getServiceTypeVersions()));
         assertEquals("serviceType",        "Web Map Server",                       String.valueOf(id.getServiceType()));
         assertEquals("abstract",           "A dummy service for testing purpose.", String.valueOf(id.getAbstract()));
         assertEquals("citation",           NilReason.MISSING,                      NilReason.forObject(id.getCitation()));
-        assertEquals("couplingType",       CouplingType.LOOSE,                     id.getCouplingType());
+        assertEquals("couplingType",       UnsupportedCodeList.valueOf("loose"),   id.getCouplingType());
 
-        final CoupledResource resource = getSingleton(id.getCoupledResources());
+        final DefaultCoupledResource resource = getSingleton(id.getCoupledResources());
 //      assertEquals("scopedName",        "mySpace:ABC-123",   …)  skipped because not present in new ISO 19115-3:2016.
 //      assertEquals("resourceReference", "WMS specification", …)  skipped because not present in legacy ISO 19139:2007.
 
-        final OperationMetadata op = resource.getOperation();
+        final DefaultOperationMetadata op = resource.getOperation();
         assertNotNull("operation", op);
         assertEquals("operationName", "Get Map", op.getOperationName());
-        assertEquals("distributedComputingPlatform", DistributedComputingPlatform.WEB_SERVICES, getSingleton(op.getDistributedComputingPlatforms()));
+        assertEquals("distributedComputingPlatform", UnsupportedCodeList.valueOf("WEB_SERVICES"), getSingleton(op.getDistributedComputingPlatforms()));
         assertEquals("connectPoints", NilReason.MISSING, NilReason.forObject(getSingleton(op.getConnectPoints())));
 
         final ParameterDescriptor<?> param = getSingleton(op.getParameters());
         assertEquals("name",          "Version",             String.valueOf(param.getName()));
         assertEquals("minimumOccurs", 0,                     param.getMinimumOccurs());
         assertEquals("maximumOccurs", 1,                     param.getMaximumOccurs());
-        assertEquals("direction",     ParameterDirection.IN, param.getDirection());
+//      assertEquals("direction",     ParameterDirection.IN, param.getDirection());
     }
 
     /**
@@ -110,9 +105,9 @@
      */
     @Test
     public void testUnmarshal() throws JAXBException {
-        final ServiceIdentification id = unmarshalFile(ServiceIdentification.class, XML2016+FILENAME);
+        final DefaultServiceIdentification id = unmarshalFile(DefaultServiceIdentification.class, XML2016+FILENAME);
         verify(id);
-        final CoupledResource resource = getSingleton(id.getCoupledResources());
+        final DefaultCoupledResource resource = getSingleton(id.getCoupledResources());
         assertTitleEquals("resourceReference", "WMS specification", getSingleton(resource.getResourceReferences()));
     }
 
@@ -123,9 +118,9 @@
      */
     @Test
     public void testUnmarshalLegacy() throws JAXBException {
-        final ServiceIdentification id = unmarshalFile(ServiceIdentification.class, XML2007+FILENAME);
+        final DefaultServiceIdentification id = unmarshalFile(DefaultServiceIdentification.class, XML2007+FILENAME);
         verify(id);
-        final CoupledResource resource = getSingleton(id.getCoupledResources());
+        final DefaultCoupledResource resource = getSingleton(id.getCoupledResources());
         assertEquals("scopedName", "mySpace:ABC-123", String.valueOf(resource.getScopedName()));
     }
 
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/quality/ScopeCodeTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/quality/ScopeCodeTest.java
index 126617c..137c841 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/quality/ScopeCodeTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/quality/ScopeCodeTest.java
@@ -17,7 +17,7 @@
 package org.apache.sis.metadata.iso.quality;
 
 import javax.xml.bind.JAXBException;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.apache.sis.test.xml.TestCase;
 import org.junit.Test;
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
index 494b9e9..3cfbd28 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
@@ -21,9 +21,8 @@
 import java.util.HashSet;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
-import org.opengis.metadata.citation.Party;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.internal.simple.CitationConstant;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.MetadataStandard;
@@ -118,22 +117,22 @@
             assertNull(name, actualID);
         } else if (actualID != null) {
             assertEquals(name, expectedID.getCode(),      actualID.getCode());
-            assertEquals(name, expectedID.getCodeSpace(), actualID.getCodeSpace());
-            assertEquals(name, expectedID.getVersion(),   actualID.getVersion());
+//          assertEquals(name, expectedID.getCodeSpace(), actualID.getCodeSpace());
+//          assertEquals(name, expectedID.getVersion(),   actualID.getVersion());
         }
         /*
          * The fallback may not declare all responsible parties.
          * If it declares a party, the name and role shall be equal.
          */
-        final Responsibility expectedResp = first(fromDB.getCitedResponsibleParties());
-        final Responsibility actualResp   = first(fromFB.getCitedResponsibleParties());
+        final ResponsibleParty expectedResp = first(fromDB.getCitedResponsibleParties());
+        final ResponsibleParty actualResp   = first(fromFB.getCitedResponsibleParties());
         if (expectedResp == null) {
             assertNull(name, actualResp);
         } else if (actualResp != null) {
             assertEquals(name, expectedResp.getRole(), actualResp.getRole());
-            final Party expectedParty = first(expectedResp.getParties());
-            final Party actualParty = first(actualResp.getParties());
-            assertEquals(name, expectedParty.getName(), actualParty.getName());
+//          final Party expectedParty = first(expectedResp.getParties());
+//          final Party actualParty = first(actualResp.getParties());
+//          assertEquals(name, expectedParty.getName(), actualParty.getName());
         }
         assertEquals(name, first(fromDB.getPresentationForms()),
                            first(fromFB.getPresentationForms()));
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
index e0179ef..e126137 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
@@ -32,7 +32,6 @@
 import org.junit.Test;
 
 import static org.junit.Assert.*;
-import static org.apache.sis.test.TestUtilities.getSingleton;
 
 
 /**
@@ -110,10 +109,8 @@
      * @param title         the expected format title.
      */
     private static void verify(final Format format, final String abbreviation, final String title) {
-        final Citation spec = format.getFormatSpecificationCitation();
-        assertNotNull("formatSpecificationCitation", spec);
-        assertEquals("abbreviation", abbreviation, String.valueOf(getSingleton(spec.getAlternateTitles())));
-        assertEquals("title", title, String.valueOf(spec.getTitle()));
+        assertEquals("abbreviation", abbreviation, String.valueOf(format.getName()));
+        assertEquals("title", title, String.valueOf(format.getSpecification()));
     }
 
     /**
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java
index 8880382..4cb97f8 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java
@@ -17,7 +17,6 @@
 package org.apache.sis.metadata.sql;
 
 import java.util.Collections;
-import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.PresentationForm;
 import org.opengis.metadata.citation.OnLineFunction;
@@ -28,7 +27,6 @@
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
 import org.apache.sis.metadata.iso.citation.DefaultTelephone;
 import org.apache.sis.metadata.MetadataStandard;
-import org.apache.sis.internal.util.URLs;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.DependsOn;
@@ -37,8 +35,7 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -86,6 +83,7 @@
      * @throws Exception if an error occurred while writing or reading the database.
      */
     @Test
+    @org.junit.Ignore("Requires GeoAPI 3.1.")
     public void testPostgreSQL() throws Exception {
         try (final TestDatabase db = TestDatabase.createOnPostgreSQL("MetadataWriter", true)) {
             source = new MetadataWriter(MetadataStandard.ISO_19115, db.source, "MetadataWriter", null);
@@ -122,7 +120,7 @@
         assertEquals("EPSG",      source.search(HardCodedCitations.EPSG));
         assertEquals("SIS",       source.search(HardCodedCitations.SIS));
         assertNull  ("ISO 19111", source.search(HardCodedCitations.ISO_19111));
-        assertEquals("{rp}EPSG",  source.search(TestUtilities.getSingleton(
+        assertEquals("EPSG",      source.search(TestUtilities.getSingleton(
                 HardCodedCitations.EPSG.getCitedResponsibleParties())));
     }
 
@@ -155,21 +153,13 @@
         /*
          * Ask for dependencies that are known to exist.
          */
-        final Responsibility responsible = TestUtilities.getSingleton(c.getCitedResponsibleParties());
+        final ResponsibleParty responsible = TestUtilities.getSingleton(c.getCitedResponsibleParties());
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, responsible.getRole());
 
-        final Party party = TestUtilities.getSingleton(responsible.getParties());
-        assertEquals("International Association of Oil & Gas Producers", party.getName().toString());
-        final Contact contact = TestUtilities.getSingleton(party.getContactInfo());
-        /*
-         * Invoke the deprecated `getOnlineResource()` method (singular form) before the non-deprecated
-         * `getOnlineResources()` (plural form) replacement. They shall give the same result no matter
-         * which form were stored in the database.
-         */
-        @SuppressWarnings("deprecation")
-        final OnlineResource resource = contact.getOnlineResource();
-        assertSame(resource, TestUtilities.getSingleton(contact.getOnlineResources()));
-        assertEquals(URLs.EPSG, resource.getLinkage().toString());
+        assertEquals("International Association of Oil & Gas Producers", responsible.getOrganisationName().toString());
+
+        OnlineResource resource = responsible.getContactInfo().getOnlineResource();
+        assertEquals("https://epsg.org/", resource.getLinkage().toString());
         assertEquals(OnLineFunction.INFORMATION, resource.getFunction());
         /*
          * Ask columns that are known to not exist.
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/xml/SchemaComplianceTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/xml/SchemaComplianceTest.java
deleted file mode 100644
index e03a2ac..0000000
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/xml/SchemaComplianceTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.metadata.xml;
-
-import java.nio.file.Path;
-import java.nio.file.Files;
-import org.apache.sis.metadata.iso.ISOMetadata;
-import org.apache.sis.internal.system.DataDirectory;
-import org.apache.sis.test.ProjectDirectories;
-import org.apache.sis.test.xml.SchemaCompliance;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-
-import static org.junit.Assume.*;
-
-
-/**
- * Tests conformance of JAXB annotations with XML schemas if those schemas are available.
- * This tests requires the {@code $SIS_DATA/Schemas/iso/19115/-3} directory to exists.
- * Those files must be installed manually; they are not distributed with Apache SIS for licensing reasons.
- * Content can be downloaded as ZIP files from <a href="https://standards.iso.org/iso/19115/">ISO portal</a>.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.0
- * @module
- */
-public final strictfp class SchemaComplianceTest extends TestCase {
-    /**
-     * Verifies compliance with metadata schemas.
-     *
-     * @throws Exception if an error occurred while checking the schema.
-     *
-     * @see <a href="https://standards.iso.org/iso/19115/-3/">ISO schemas for metadata</a>
-     */
-    @Test
-    public void verifyMetadata() throws Exception {
-        Path directory = DataDirectory.SCHEMAS.getDirectory();
-        assumeNotNull(directory);
-        directory = directory.resolve("iso");
-        assumeTrue(Files.isDirectory(directory.resolve("19115")));
-        /*
-         * Locate the root of metadata class directory. In a Maven build:
-         * "core/sis-metadata/target/classes/org/apache/sis/metadata/iso"
-         */
-        final ProjectDirectories dir = new ProjectDirectories(ISOMetadata.class);
-        final SchemaCompliance checker = new SchemaCompliance(dir.classesRootDirectory, directory);
-        checker.loadDefaultSchemas();
-        checker.verify(dir.classesPackageDirectory);
-    }
-}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java b/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
index 29b2912..b900302 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
@@ -23,9 +23,7 @@
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.lineage.Source;
-import org.opengis.metadata.maintenance.Scope;
 import org.opengis.metadata.maintenance.ScopeCode;
-import org.opengis.metadata.content.FeatureTypeInfo;
 import org.opengis.metadata.content.FeatureCatalogueDescription;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.test.xml.DocumentComparator;
@@ -35,7 +33,12 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
 // Branch-specific imports
-import org.opengis.metadata.citation.Responsibility;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+import org.apache.sis.metadata.iso.content.DefaultFeatureCatalogueDescription;
+import org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo;
+import org.apache.sis.metadata.iso.lineage.DefaultSource;
+import org.apache.sis.metadata.iso.maintenance.DefaultScope;
 
 
 /**
@@ -82,9 +85,9 @@
      *
      * @since 0.8
      */
-    public static void assertPartyNameEquals(final String message, final String expected, final Citation citation) {
+    public static void assertPartyNameEquals(final String message, final String expected, final DefaultCitation citation) {
         assertNotNull(message, citation);
-        final Responsibility r = getSingleton(citation.getCitedResponsibleParties());
+        final DefaultResponsibility r = (DefaultResponsibility) getSingleton(citation.getCitedResponsibleParties());
         final InternationalString name = getSingleton(r.getParties()).getName();
         assertNotNull(message, name);
         assertEquals(message, expected, name.toString(Locale.US));
@@ -101,7 +104,7 @@
      * @since 1.0
      */
     public static void assertContentInfoEquals(final String name, final Integer count, final FeatureCatalogueDescription catalog) {
-        final FeatureTypeInfo info = getSingleton(catalog.getFeatureTypeInfo());
+        final DefaultFeatureTypeInfo info = getSingleton(((DefaultFeatureCatalogueDescription) catalog).getFeatureTypeInfo());
         assertEquals("metadata.contentInfo.featureType", name, String.valueOf(info.getFeatureTypeName()));
         assertEquals("metadata.contentInfo.featureInstanceCount", count, info.getFeatureInstanceCount());
     }
@@ -118,10 +121,10 @@
      */
     public static void assertFeatureSourceEquals(final String name, final String[] features, final Source source) {
         assertEquals("metadata.lineage.source.sourceCitation.title", name, String.valueOf(source.getSourceCitation().getTitle()));
-        final Scope scope = source.getScope();
+        final DefaultScope scope = (DefaultScope) ((DefaultSource) source).getScope();
         assertNotNull("metadata.lineage.source.scope", scope);
         assertEquals("metadata.lineage.source.scope.level", ScopeCode.FEATURE_TYPE, scope.getLevel());
-        final CharSequence[] actual = CollectionsExt.toArray(getSingleton(scope.getLevelDescription()).getFeatures(), CharSequence.class);
+        final Object[] actual = CollectionsExt.toArray(getSingleton(scope.getLevelDescription()).getFeatures(), Object.class);
         for (int i=0; i<actual.length; i++) {
             actual[i] = actual[i].toString();
         }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/CoordinateSystemAxisMock.java b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/CoordinateSystemAxisMock.java
index 2d88290..e270d19 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/CoordinateSystemAxisMock.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/CoordinateSystemAxisMock.java
@@ -64,6 +64,8 @@
     @Override public int                  getDimension()         {return 1;}
     @Override public CoordinateSystemAxis getAxis(int dimension) {return this;}
     @Override public AxisDirection        getDirection()         {return null;}
+    @Override public double               getMinimumValue()      {return Double.NEGATIVE_INFINITY;}
+    @Override public double               getMaximumValue()      {return Double.POSITIVE_INFINITY;}
     @Override public RangeMeaning         getRangeMeaning()      {return RangeMeaning.EXACT;}
     @Override public Unit<?>              getUnit()              {return null;}
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/IdentifiedObjectMock.java b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/IdentifiedObjectMock.java
index 42e620b..de13618 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/IdentifiedObjectMock.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/IdentifiedObjectMock.java
@@ -17,12 +17,15 @@
 package org.apache.sis.test.mock;
 
 import java.util.Arrays;
+import java.util.Set;
 import java.util.Collection;
 import java.io.Serializable;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.GenericName;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.internal.util.Strings;
@@ -125,6 +128,26 @@
     }
 
     /**
+     * Returns the namespace version ({@code null} for now).
+     *
+     * @return the namespace version.
+     */
+    @Override
+    public final String getVersion() {
+        return null;
+    }
+
+    /**
+     * Returns the authority that define the object ({@code null} for now).
+     *
+     * @return the defining authority.
+     */
+    @Override
+    public final Citation getAuthority() {
+        return null;
+    }
+
+    /**
      * Returns {@link #alias} in an unmodifiable collection, or an empty collection if the alias is null.
      *
      * @return {@link #alias} singleton or an empty collection.
@@ -135,6 +158,37 @@
     }
 
     /**
+     * Returns the identifiers (currently null).
+     *
+     * @return the identifiers of this object.
+     */
+    @Override
+    public final Set<ReferenceIdentifier> getIdentifiers() {
+        return null;
+    }
+
+    /**
+     * Returns the remarks (currently null).
+     *
+     * @return the remarks associated to this object.
+     */
+    @Override
+    public final InternationalString getRemarks() {
+        return null;
+    }
+
+    /**
+     * Returns the WKT representation (currently none).
+     *
+     * @return the WKT representation of this object.
+     * @throws UnsupportedOperationException if there is no WKT representation.
+     */
+    @Override
+    public final String toWKT() throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Returns a string representation for debugging purpose.
      */
     @Override
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java
index 634f1e2..5479bc2 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java
@@ -16,11 +16,9 @@
  */
 package org.apache.sis.test.mock;
 
-import java.util.Map;
+import java.util.Locale;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Locale;
-import java.nio.charset.Charset;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@@ -69,22 +67,11 @@
     }
 
     /**
-     * Returns {@link #language} in a singleton map or an empty map.
-     *
-     * @return {@link #language}
-     */
-    @Override
-    public Map<Locale,Charset> getLocalesAndCharsets() {
-        return (language != null) ? Collections.singletonMap(language, null) : Collections.emptyMap();
-    }
-
-    /**
      * Returns {@link #language} in a singleton set or an empty set.
      *
      * @return {@link #language}
      */
     @Override
-    @Deprecated
     public Collection<Locale> getLanguages() {
         return (language != null) ? Collections.singleton(language) : Collections.emptySet();
     }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/VerticalCRSMock.java b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/VerticalCRSMock.java
index 203fd55..c74d5aa 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/VerticalCRSMock.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/VerticalCRSMock.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.test.mock;
 
+import java.util.Date;
 import javax.measure.Unit;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.referencing.crs.VerticalCRS;
@@ -121,6 +122,8 @@
 
     @Override public String               getAbbreviation()      {return up ? "h" : "d";}
     @Override public InternationalString  getScope()             {return null;}
+    @Override public InternationalString  getAnchorPoint()       {return null;}
+    @Override public Date                 getRealizationEpoch()  {return null;}
     @Override public Extent               getDomainOfValidity()  {return null;}
     @Override public VerticalDatumType    getVerticalDatumType() {return type;}
     @Override public VerticalDatum        getDatum()             {return this;}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java b/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
index 2711a6c..7b1c1e1 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
@@ -136,8 +136,7 @@
     org.apache.sis.metadata.sql.IdentifierGeneratorTest.class,
     org.apache.sis.metadata.sql.MetadataSourceTest.class,
     org.apache.sis.metadata.sql.MetadataWriterTest.class,
-    org.apache.sis.metadata.iso.citation.CitationsTest.class,
-    org.apache.sis.metadata.xml.SchemaComplianceTest.class
+    org.apache.sis.metadata.iso.citation.CitationsTest.class
 })
 public final strictfp class MetadataTestSuite extends TestSuite {
     /**
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
index f9d8a2e..2fddaa9 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
@@ -31,12 +31,9 @@
 import javax.xml.bind.annotation.XmlElementRefs;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.annotation.UML;
-import org.opengis.annotation.Classifier;
-import org.opengis.annotation.Stereotype;
 import org.opengis.annotation.Obligation;
 import org.opengis.annotation.Specification;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.internal.xml.LegacyNamespaces;
@@ -68,7 +65,6 @@
  * </ul>
  *
  * This class does not verify JAXB annotations against a XSD file.
- * For such verification, see {@link SchemaCompliance}.
  *
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -227,13 +223,12 @@
      * <p>The default implementation recognizes the
      * {@linkplain Specification#ISO_19115   ISO 19115},
      * {@linkplain Specification#ISO_19115_2 ISO 19115-2},
-     * {@linkplain Specification#ISO_19115_3 ISO 19115-3},
      * {@linkplain Specification#ISO_19139   ISO 19139} and
      * {@linkplain Specification#ISO_19108   ISO 19108} specifications,
      * with a hard-coded list of exceptions to the general rule.
      * Subclasses shall override this method if they need to support more namespaces.</p>
      *
-     * <p>Note that a more complete verification is done by {@link SchemaCompliance}.
+     * <p>Note that a more complete verification is done by {@code SchemaCompliance}.
      * But the test done in this {@link AnnotationConsistencyCheck} class can be run without network access.</p>
      *
      * <p>The prefix for the given namespace will be fetched by
@@ -243,8 +238,10 @@
      * @param  uml   the UML associated to the class or the method.
      * @return the expected namespace.
      * @throws IllegalArgumentException if the given UML is unknown to this method.
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
-    @SuppressWarnings("deprecation")
+    @Deprecated
     protected String getExpectedNamespaceStart(final Class<?> impl, final UML uml) {
         final String identifier = uml.identifier();
         switch (identifier) {
@@ -253,13 +250,11 @@
             case "SV_OperationChainMetadata":
             case "SV_ServiceIdentification": {              // Historical reasons (other standard integrated into ISO 19115)
                 assertEquals("Unexpected @Specification value.", Specification.ISO_19115, uml.specification());
-                assertEquals("Specification version should be latest ISO 19115.", (short) 0, uml.version());
                 return Namespaces.SRV;
             }
             case "DQ_TemporalAccuracy":                     // Renamed DQ_TemporalQuality
             case "DQ_NonQuantitativeAttributeAccuracy": {   // Renamed DQ_NonQuantitativeAttributeCorrectness
                 assertEquals("Unexpected @Specification value.", Specification.ISO_19115, uml.specification());
-                assertEquals("Specification version should be legacy ISO 19115.", (short) 2003, uml.version());
                 return LegacyNamespaces.GMD;
             }
             case "role": {
@@ -311,12 +306,11 @@
          */
         if (identifier.startsWith("DQ_")) {
             assertEquals("Unexpected @Specification value.", Specification.ISO_19115, uml.specification());
-            assertEquals("Specification version should be legacy ISO 19115.", (short) 2003, uml.version());
             return Namespaces.MDQ;
         }
         if (identifier.startsWith("QE_")) {
             assertEquals("Unexpected @Specification value.", Specification.ISO_19115_2, uml.specification());
-            switch (uml.version()) {
+            switch (/*uml.version()*/ 0) {
                 case 0:    return Namespaces.MDQ;
                 case 2009: return LegacyNamespaces.GMI;
                 default: fail("Unexpected version number in " + uml);
@@ -332,7 +326,7 @@
          * General cases (after we processed all the special cases)
          * based on which standard defines the type or property.
          */
-        if (uml.version() != 0) {
+        if (/*uml.version()*/ 0 != 0) {
             switch (uml.specification()) {
                 case ISO_19115:   return LegacyNamespaces.GMD;
                 case ISO_19115_2: return LegacyNamespaces.GMI;
@@ -340,8 +334,7 @@
         }
         switch (uml.specification()) {
             case ISO_19115:
-            case ISO_19115_2:
-            case ISO_19115_3: return CodeListUID.METADATA_ROOT;
+            case ISO_19115_2: return CodeListUID.METADATA_ROOT;
             case ISO_19139:   return LegacyNamespaces.GMX;
             case ISO_19108:   return LegacyNamespaces.GMD;
             default: throw new IllegalArgumentException(uml.toString());
@@ -355,18 +348,17 @@
      * and unconditionally with {@code "_Type"} appended.
      * Subclasses shall override this method when mismatches are known to exist between the UML and XML type names.
      *
-     * @param  stereotype  the stereotype of the interface, or {@code null} if none.
-     * @param  uml         the UML of the interface for which to get the corresponding XML type name.
+     * @param  uml  the UML of the interface for which to get the corresponding XML type name.
      * @return the name of the XML type for the given element, or {@code null} if none.
      *
      * @see #testImplementationAnnotations()
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
-    protected String getExpectedXmlTypeName(final Stereotype stereotype, final UML uml) {
+    @Deprecated
+    protected String getExpectedXmlTypeName(final UML uml) {
         final String rootName = uml.identifier();
         final StringBuilder buffer = new StringBuilder(rootName.length() + 13);
-        if (Stereotype.ABSTRACT.equals(stereotype)) {
-            buffer.append("Abstract");
-        }
         return buffer.append(rootName).append("_Type").toString();
     }
 
@@ -375,16 +367,19 @@
      * The default implementation returns {@link UML#identifier()}, possibly with {@code "Abstract"} prepended.
      * Subclasses shall override this method when mismatches are known to exist between the UML and XML element names.
      *
-     * @param  stereotype  the stereotype of the interface, or {@code null} if none.
-     * @param  uml         the UML of the interface for which to get the corresponding XML root element name.
+     * @param  uml  the UML of the interface for which to get the corresponding XML root element name.
      * @return the name of the XML root element for the given UML.
      *
      * @see #testImplementationAnnotations()
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
-    protected String getExpectedXmlRootElementName(final Stereotype stereotype, final UML uml) {
+    @Deprecated
+    protected String getExpectedXmlRootElementName(final UML uml) {
         String name = uml.identifier();
-        if (Stereotype.ABSTRACT.equals(stereotype)) {
-            name = "Abstract".concat(name);
+        switch (name) {
+            // This case can be removed if https://issues.apache.org/jira/browse/SIS-398 is fixed.
+            case "MI_PolarizationOrientationCode": name = "MI_PolarisationOrientationCode"; break;
         }
         return name;
     }
@@ -424,6 +419,24 @@
                 }
                 break;
             }
+            case "detectedPolarization": {
+                if (org.opengis.metadata.content.Band.class.isAssignableFrom(enclosing)) {
+                    name = "detectedPolarisation";          // Spelling change in XSD files
+                }
+                break;
+            }
+            case "transmittedPolarization": {
+                if (org.opengis.metadata.content.Band.class.isAssignableFrom(enclosing)) {
+                    name = "transmittedPolarisation";       // Spelling change in XSD files
+                }
+                break;
+            }
+            case "featureType": {
+                if (org.opengis.metadata.distribution.DataFile.class.isAssignableFrom(enclosing)) {
+                    name = "featureTypes";                  // Spelling change in XSD files
+                }
+                break;
+            }
             case "valueType": {
                 if (org.opengis.metadata.quality.Result.class.isAssignableFrom(enclosing)) {
                     return "valueRecordType";
@@ -591,7 +604,7 @@
             case "getNominalSpatialResolution":
             case "getTransferFunctionType": {
                 final Class<?> dc = method.getDeclaringClass();
-                return org.opengis.metadata.content.SampleDimension.class.isAssignableFrom(dc)
+                return org.apache.sis.metadata.iso.content.DefaultSampleDimension.class.isAssignableFrom(dc)
                         && !org.opengis.metadata.content.Band.class.isAssignableFrom(dc);
             }
             /*
@@ -622,7 +635,7 @@
             testingClass = type.getCanonicalName();
             UML uml = type.getAnnotation(UML.class);
             assertNotNull("Missing @UML annotation.", uml);
-            if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (!CodeList.class.isAssignableFrom(type)) {
                 for (final Method method : type.getDeclaredMethods()) {
                     if (isPublic(method)) {
                         testingMethod = method.getName();
@@ -651,7 +664,7 @@
     public void testPackageAnnotations() {
         final Set<Package> packages = new HashSet<>();
         for (final Class<?> type : types) {
-            if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (!CodeList.class.isAssignableFrom(type)) {
                 testingClass = type.getCanonicalName();
                 final Class<?> impl = getImplementation(type);
                 if (impl != null) {
@@ -692,7 +705,7 @@
     @DependsOnMethod("testInterfaceAnnotations")
     public void testImplementationAnnotations() {
         for (final Class<?> type : types) {
-            if (ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (CodeList.class.isAssignableFrom(type)) {
                 // Skip code lists, since they are not the purpose of this test.
                 continue;
             }
@@ -715,14 +728,7 @@
             final XmlRootElement root = impl.getAnnotation(XmlRootElement.class);
             assertNotNull("Missing @XmlRootElement annotation.", root);
             final UML uml = type.getAnnotation(UML.class);
-            Stereotype stereotype = null;
-            if (uml != null) {
-                final Classifier c = type.getAnnotation(Classifier.class);
-                if (c != null) {
-                    stereotype = c.value();
-                }
-                assertEquals("Wrong @XmlRootElement.name().", getExpectedXmlRootElementName(stereotype, uml), root.name());
-            }
+            // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
             /*
              * Check that the namespace is the expected one (according subclass)
              * and is not redundant with the package @XmlSchema annotation.
@@ -733,11 +739,7 @@
              */
             final XmlType xmlType = impl.getAnnotation(XmlType.class);
             assertNotNull("Missing @XmlType annotation.", xmlType);
-            String expected = getExpectedXmlTypeName(stereotype, uml);
-            if (expected == null) {
-                expected = DEFAULT;
-            }
-            assertEquals("Wrong @XmlType.name().", expected, xmlType.name());
+            // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
         }
     }
 
@@ -755,7 +757,7 @@
     @DependsOnMethod("testImplementationAnnotations")
     public void testMethodAnnotations() {
         for (final Class<?> type : types) {
-            if (ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (CodeList.class.isAssignableFrom(type)) {
                 // Skip code lists, since they are not the purpose of this test.
                 continue;
             }
@@ -829,9 +831,7 @@
                  */
                 if (uml != null) {
                     assertEquals("Wrong @XmlElement.name().", getExpectedXmlElementName(type, uml), element.name());
-                    if (!method.isAnnotationPresent(Deprecated.class) && uml.version() == 0) {
-                        assertEquals("Wrong @XmlElement.required().", uml.obligation() == Obligation.MANDATORY, element.required());
-                    }
+                    // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
                 }
                 /*
                  * Check that the namespace is the expected one (according subclass)
@@ -917,10 +917,10 @@
                 assertFalse("Expected @XmlElementRef.", wrapper.isInherited);
                 final UML uml = type.getAnnotation(UML.class);
                 if (uml != null) {                  // 'assertNotNull' is 'testInterfaceAnnotations()' job.
-                    assertEquals("Wrong @XmlElement.", getExpectedXmlRootElementName(null, uml), element.name());
+                    assertEquals("Wrong @XmlElement.", getExpectedXmlRootElementName(uml), element.name());
                 }
                 final String namespace = assertExpectedNamespace(element.namespace(), wrapper.type, uml);
-                if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+                if (!CodeList.class.isAssignableFrom(type)) {
                     final String expected = getNamespace(getImplementation(type));
                     if (expected != null) {         // 'assertNotNull' is 'testImplementationAnnotations()' job.
                         assertEquals("Inconsistent @XmlRootElement namespace.", expected, namespace);
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java
deleted file mode 100644
index 690edc5..0000000
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- * 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.test.xml;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.HashMap;
-import java.util.Collection;
-import java.util.Collections;
-import java.io.IOException;
-import java.lang.reflect.Type;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.ParameterizedType;
-import javax.xml.bind.annotation.XmlNs;
-import javax.xml.bind.annotation.XmlType;
-import javax.xml.bind.annotation.XmlSchema;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
-import javax.xml.parsers.ParserConfigurationException;
-import org.xml.sax.SAXException;
-import org.opengis.util.CodeList;
-import org.opengis.annotation.UML;
-import org.opengis.geoapi.SchemaException;
-import org.opengis.geoapi.SchemaInformation;
-import org.apache.sis.util.Classes;
-import org.apache.sis.internal.system.Modules;
-import org.apache.sis.internal.util.Constants;
-import org.apache.sis.internal.xml.LegacyNamespaces;
-import org.apache.sis.xml.Namespaces;
-
-import static org.apache.sis.test.TestCase.PENDING_FUTURE_SIS_VERSION;
-
-
-/**
- * Verifies JAXB annotations in a single package. A new instance of this class is created by
- * {@link SchemaCompliance#verify(java.nio.file.Path)} for each Java package to be verified.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.0
- * @module
- */
-final strictfp class PackageVerifier {
-    /**
-     * Sentinel value used in {@link #LEGACY_NAMESPACES} for meaning "all properties in that namespace".
-     */
-    @SuppressWarnings("unchecked")
-    private static final Set<String> ALL = InfiniteSet.INSTANCE;
-
-    /**
-     * Classes or properties having a JAXB annotation in this namespace should be deprecated.
-     * Deprecated namespaces are enumerated as keys. If the associated value is {@link #ALL},
-     * the whole namespace is deprecated. If the value is not ALL, then only the enumerated
-     * properties are deprecated.
-     *
-     * <p>Non-ALL values are rare. They happen in a few cases where a property is legacy despite its namespace.
-     * Those "properties" are errors in the legacy ISO 19139:2007 schema; they were types without their property
-     * wrappers. For example in {@code SV_CoupledResource}, {@code <gco:ScopedName>} was marshalled without its
-     * {@code <srv:scopedName>} wrapper — note the upper and lower-case "s". Because {@code ScopedName} is a type,
-     * we had to keep the namespace declared in {@link org.apache.sis.util.iso.DefaultScopedName}
-     * (the replacement is performed by {@code org.apache.sis.xml.TransformingWriter}).</p>
-     */
-    private static final Map<String, Set<String>> LEGACY_NAMESPACES;
-    static {
-        final Map<String, Set<String>> m = new HashMap<>(8);
-        m.put(LegacyNamespaces.GMD, ALL);
-        m.put(LegacyNamespaces.GMI, ALL);
-        m.put(LegacyNamespaces.GMX, ALL);
-        m.put(LegacyNamespaces.SRV, ALL);
-        m.put(Namespaces.GCO, Collections.singleton("ScopedName"));     // Not to be confused with standard <srv:scopedName>
-        LEGACY_NAMESPACES = Collections.unmodifiableMap(m);
-    }
-
-    /**
-     * Types declared in JAXB annotations to be considered as equivalent to types in XML schemas.
-     */
-    private static final Map<String,String> TYPE_EQUIVALENCES;
-    static {
-        final Map<String,String> m = new HashMap<>();
-        m.put("PT_FreeText",             "CharacterString");
-        m.put("Abstract_Citation",       "CI_Citation");
-        m.put("AbstractCI_Party",        "CI_Party");
-        m.put("Abstract_Responsibility", "CI_Responsibility");
-        m.put("Abstract_Extent",         "EX_Extent");
-        TYPE_EQUIVALENCES = Collections.unmodifiableMap(m);
-    }
-
-    /**
-     * The schemas to compare with the JAXB annotations.
-     * Additional schemas will be loaded as needed.
-     */
-    private final SchemaCompliance schemas;
-
-    /**
-     * The package name, for reporting error.
-     */
-    private final String packageName;
-
-    /**
-     * The default namespace to use if a class does not define explicitly a namespace.
-     */
-    private final String packageNS;
-
-    /**
-     * The namespace of the class under examination.
-     * This field must be updated for every class found in a package.
-     */
-    private String classNS;
-
-    /**
-     * The class under examination, used in error messages.
-     * This field must be updated for every class found in a package.
-     */
-    private Class<?> currentClass;
-
-    /**
-     * Whether the class under examination is defined in a legacy namespace.
-     * In such case, some checks may be skipped because we didn't loaded schemas for legacy properties.
-     */
-    private boolean isDeprecatedClass;
-
-    /**
-     * The schema definition for the class under examination.
-     *
-     * @see SchemaCompliance#getTypeDefinition(String)
-     */
-    private Map<String, SchemaCompliance.Element> properties;
-
-    /**
-     * Whether a namespace is actually used of not.
-     * We use this map for identifying unnecessary prefix declarations.
-     */
-    private final Map<String,Boolean> namespaceIsUsed;
-
-    /**
-     * Whether adapters declared in {@code package-info.java} are used or not.
-     */
-    private final Map<Class<?>,Boolean> adapterIsUsed;
-
-    /**
-     * Creates a new verifier for the given package.
-     */
-    PackageVerifier(final SchemaCompliance schemas, final Package pkg)
-            throws IOException, ParserConfigurationException, SAXException, SchemaException
-    {
-        this.schemas = schemas;
-        namespaceIsUsed = new HashMap<>();
-        adapterIsUsed = new HashMap<>();
-        String name = "?", namespace = "";
-        if (pkg != null) {
-            name = pkg.getName();
-            final XmlSchema schema = pkg.getAnnotation(XmlSchema.class);
-            if (schema != null) {
-                namespace = schema.namespace();
-                String location = schema.location();
-                if (!XmlSchema.NO_LOCATION.equals(location)) {
-                    String expected = location;
-                    if (expected.startsWith(Constants.HTTPS)) {
-                        // Replace "https" (used for schema location) by "http" (used for namespace).
-                        expected = Constants.HTTP + expected.substring(Constants.HTTPS.length());
-                    }
-                    if (!expected.startsWith(schema.namespace())) {
-                        throw new SchemaException("XML schema location inconsistent with namespace in package " + name);
-                    }
-                    schemas.loadSchema(location);
-                }
-                for (final XmlNs xmlns : schema.xmlns()) {
-                    final String pr = xmlns.prefix();
-                    final String ns = xmlns.namespaceURI();
-                    final String cr = schemas.allXmlNS.put(pr, ns);
-                    if (cr != null && !cr.equals(ns)) {
-                        throw new SchemaException(String.format("Prefix \"%s\" associated to two different namespaces:%n%s%n%s", pr, cr, ns));
-                    }
-                    if (namespaceIsUsed.put(ns, Boolean.FALSE) != null) {
-                        throw new SchemaException(String.format("Duplicated namespace in package %s:%n%s", name, ns));
-                    }
-                }
-            }
-            /*
-             * Lists the type of all values for which an adapter is declared in package-info.
-             * If the type is not explicitly declared, then it is inferred from class signature.
-             */
-            final XmlJavaTypeAdapters adapters = pkg.getAnnotation(XmlJavaTypeAdapters.class);
-            if (adapters != null) {
-                for (final XmlJavaTypeAdapter adapter : adapters.value()) {
-                    Class<?> propertyType = adapter.type();
-                    if (propertyType == XmlJavaTypeAdapter.DEFAULT.class) {
-                        for (Class<?> c = adapter.value(); ; c = c.getSuperclass()) {
-                            final Type type = c.getGenericSuperclass();
-                            if (type == null) {
-                                throw new SchemaException(String.format(
-                                        "Can not infer type for %s adapter.", adapter.value().getName()));
-                            }
-                            if (type instanceof ParameterizedType) {
-                                final Type[] p = ((ParameterizedType) type).getActualTypeArguments();
-                                if (p.length == 2) {
-                                    Type pt = p[1];
-                                    if (pt instanceof ParameterizedType) {
-                                        pt = ((ParameterizedType) pt).getRawType();
-                                    }
-                                    if (pt instanceof Class<?>) {
-                                        propertyType = (Class<?>) pt;
-                                        break;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    if (adapterIsUsed.put((Class<?>) propertyType, Boolean.FALSE) != null) {
-                        throw new SchemaException(String.format(
-                                "More than one adapter for %s in package %s", propertyType, name));
-                    }
-                }
-            }
-        }
-        packageName = name;
-        packageNS = namespace;
-    }
-
-    /**
-     * Verifies {@code @XmlType} and {@code @XmlRootElement} on the class. This method verifies naming convention
-     * (type name should be same as root element name with {@value SchemaCompliance#TYPE_SUFFIX} suffix appended),
-     * ensures that the name exists in the schema, and checks the namespace.
-     *
-     * @param  type  the class on which to verify annotations.
-     */
-    final void verify(final Class<?> type)
-            throws IOException, ParserConfigurationException, SAXException, SchemaException
-    {
-        /*
-         * Reinitialize fields to be updated for each class.
-         */
-        classNS           = null;
-        currentClass      = type;
-        isDeprecatedClass = false;
-        properties        = Collections.emptyMap();
-
-        final XmlType        xmlType = type.getDeclaredAnnotation(XmlType.class);
-        final XmlRootElement xmlRoot = type.getDeclaredAnnotation(XmlRootElement.class);
-        XmlElement codeList = null;
-        /*
-         * Get the type name and namespace from the @XmlType or @XmlRootElement annotations.
-         * If both of them are present, verify that they are consistent (same namespace and
-         * same name with "_Type" suffix in @XmlType). If the type name is not declared, we
-         * assume that it is the same than the class name (this is what Apache SIS 1.0 does
-         * in its org.apache.sis.internal.jaxb.code package for CodeList adapters).
-         */
-        final String isoName;       // ISO class name (not the same than Java class name).
-        if (xmlRoot != null) {
-            classNS = xmlRoot.namespace();
-            isoName = xmlRoot.name();
-            if (xmlType != null) {
-                if (!classNS.equals(xmlType.namespace())) {
-                    throw new SchemaException(errorInClassMember(null)
-                            .append("Mismatched namespace in @XmlType and @XmlRootElement.").toString());
-                }
-                SchemaCompliance.verifyNamingConvention(type.getName(), isoName, xmlType.name(), SchemaCompliance.TYPE_SUFFIX);
-            }
-        } else if (xmlType != null) {
-            classNS = xmlType.namespace();
-            final String name = xmlType.name();
-            isoName = SchemaCompliance.trim(name, SchemaCompliance.TYPE_SUFFIX);
-        } else {
-            /*
-             * If there is neither @XmlRootElement or @XmlType annotation, it may be a code list as implemented
-             * in the org.apache.sis.internal.jaxb.code package. Those adapters have a single @XmlElement which
-             * is to be interpreted as if it was the actual type.
-             */
-            for (final Method method : type.getDeclaredMethods()) {
-                final XmlElement e = method.getDeclaredAnnotation(XmlElement.class);
-                if (e != null) {
-                    if (codeList != null) return;
-                    codeList = e;
-                }
-            }
-            if (codeList == null) return;
-            classNS = codeList.namespace();
-            isoName = codeList.name();
-        }
-        /*
-         * Verify that the namespace declared on the class is not redundant with the namespace
-         * declared in the package. Actually redundant namespaces are not wrong, but we try to
-         * reduce code size.
-         */
-        if (classNS.equals(AnnotationConsistencyCheck.DEFAULT)) {
-            classNS = packageNS;
-        } else if (classNS.equals(packageNS)) {
-            throw new SchemaException(errorInClassMember(null)
-                    .append("Redundant namespace declaration: ").append(classNS).toString());
-        }
-        /*
-         * Verify that the namespace has a prefix associated to it in the package-info file.
-         */
-        if (namespaceIsUsed.put(classNS, Boolean.TRUE) == null) {
-            throw new SchemaException(errorInClassMember(null)
-                    .append("No prefix in package-info for ").append(classNS).toString());
-        }
-        /*
-         * Properties in the legacy GMD or GMI namespaces may be deprecated, depending if a replacement
-         * is already available or not. However properties in other namespaces should not be deprecated.
-         * Some validations of deprecated properties are skipped because we didn't loaded their schema.
-         */
-        isDeprecatedClass = (LEGACY_NAMESPACES.get(classNS) == ALL);
-        if (!isDeprecatedClass) {
-            if (type.isAnnotationPresent(Deprecated.class)) {
-                throw new SchemaException(errorInClassMember(null)
-                        .append("Unexpected @Deprecated annotation.").toString());
-            }
-            /*
-             * Verify that class name exists, then verify its namespace (associated to the null key by convention).
-             */
-            properties = schemas.getTypeDefinition(isoName);
-            if (properties == null) {
-                throw new SchemaException(errorInClassMember(null)
-                        .append("Unknown name declared in @XmlRootElement: ").append(isoName).toString());
-            }
-            final String expectedNS = properties.get(null).namespace;
-            if (!classNS.equals(expectedNS)) {
-                throw new SchemaException(errorInClassMember(null)
-                        .append(isoName).append(" shall be associated to namespace ").append(expectedNS).toString());
-            }
-            if (codeList != null) return;                   // If the class was a code list, we are done.
-        }
-        /*
-         * At this point the classNS, className, isDeprecatedClass and properties field have been set.
-         * We can now loop over the XML elements, which may be on fields or on methods (public or private).
-         */
-        for (final Field field : type.getDeclaredFields()) {
-            Class<?> valueType = field.getType();
-            final boolean isCollection = Collection.class.isAssignableFrom(valueType);
-            if (isCollection) {
-                valueType = Classes.boundOfParameterizedProperty(field);
-            }
-            verify(field, field.getName(), valueType, isCollection);
-        }
-        for (final Method method : type.getDeclaredMethods()) {
-            Class<?> valueType = method.getReturnType();
-            final boolean isCollection = Collection.class.isAssignableFrom(valueType);
-            if (isCollection) {
-                valueType = Classes.boundOfParameterizedProperty(method);
-            }
-            verify(method, method.getName(), valueType, isCollection);
-        }
-    }
-
-    /**
-     * Validate a field or a method against the expected schema.
-     *
-     * @param  property      the field or method to validate.
-     * @param  javaName      the field name or method name in Java code.
-     * @param  valueType     the field type or the method return type, or element type in case of collection.
-     * @param  isCollection  whether the given value type is the element type of a collection.
-     */
-    private void verify(final AnnotatedElement property, final String javaName,
-            final Class<?> valueType, final boolean isCollection) throws SchemaException
-    {
-        final XmlElement element = property.getDeclaredAnnotation(XmlElement.class);
-        if (element == null) {
-            return;                               // No @XmlElement annotation - skip this property.
-        }
-        String name = element.name();
-        if (name.equals(AnnotationConsistencyCheck.DEFAULT)) {
-            name = javaName;
-        }
-        String ns = element.namespace();
-        if (ns.equals(AnnotationConsistencyCheck.DEFAULT)) {
-            ns = classNS;
-        }
-        if (namespaceIsUsed.put(ns, Boolean.TRUE) == null) {
-            throw new SchemaException(errorInClassMember(javaName)
-                    .append("Missing @XmlNs for namespace ").append(ns).toString());
-        }
-        /*
-         * Remember that we need an adapter for this property, unless the method or field defines its own adapter.
-         * In theory we do not need to report missing adapter since JAXB performs its own check, but we do anyway
-         * because JAXB has default adapters for String, Double, Boolean, Date, etc. which do not match the way
-         * OGC/ISO marshal those elements.
-         */
-        if (!property.isAnnotationPresent(XmlJavaTypeAdapter.class) && valueType != null) {
-            /*
-             * Internal classes in Apache SIS "jaxb" subpackages can be marshalled directly.
-             * Apache SIS classes defined in other packages may be code lists, which still need adapters.
-             */
-            if (!valueType.getName().startsWith(Modules.CLASSNAME_PREFIX) || CodeList.class.isAssignableFrom(valueType)) {
-                Class<?> c = valueType;
-                while (adapterIsUsed.replace(c, Boolean.TRUE) == null) {
-                    final Class<?> parent = c.getSuperclass();
-                    if (parent != null) {
-                        c = parent;
-                    } else {
-                        final Class<?>[] p = c.getInterfaces();
-                        if (p.length == 0) {
-                            if (valueType == org.opengis.metadata.Obligation.class)  {
-                                break;
-                            }
-                            throw new SchemaException(errorInClassMember(javaName)
-                                    .append("Missing @XmlJavaTypeAdapter for ").append(valueType).toString());
-                        }
-                        c = p[0];       // Take only the first interface, which should be the "main" parent.
-                    }
-                }
-            }
-        }
-        /*
-         * We do not verify fully the properties in legacy namespaces because we didn't loaded their schemas.
-         * However we verify at least that those properties are not declared as required.
-         */
-        if (LEGACY_NAMESPACES.getOrDefault(ns, Collections.emptySet()).contains(name)) {
-            if (!isDeprecatedClass && element.required()) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Legacy property should not be required.").toString());
-            }
-        } else {
-            /*
-             * Property in non-legacy namespaces should not be deprecated. Verify also their namespace
-             * and whether the property is required or optional, and whether it should be a collection.
-             */
-            if (property.isAnnotationPresent(Deprecated.class)) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Unexpected deprecation status.").toString());
-            }
-            final SchemaCompliance.Element info = properties.get(name);
-            if (info == null) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Unexpected XML element: ").append(name).toString());
-            }
-            if (info.namespace != null && !ns.equals(info.namespace)) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Declared namespace: ").append(ns).append(System.lineSeparator())
-                        .append("Expected namespace: ").append(info.namespace).toString());
-            }
-            if (element.required() != info.isRequired) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Expected @XmlElement(required = ").append(info.isRequired).append(')').toString());
-            }
-            /*
-             * Following is a continuation of our check for multiplicity, but also the beginning of the check
-             * for return value type. The return type should be an interface with a UML annotation; we check
-             * that this annotation contains the name of the expected type.
-             */
-            if (isCollection) {
-                if (!info.isCollection) {
-                    if (PENDING_FUTURE_SIS_VERSION)  // Temporarily disabled because require GeoAPI modifications.
-                    throw new SchemaException(errorInClassMember(javaName).append("Value should be a singleton.").toString());
-                }
-            } else if (info.isCollection) {
-                if (PENDING_FUTURE_SIS_VERSION)  // Temporarily disabled because require GeoAPI modifications.
-                throw new SchemaException(errorInClassMember(javaName).append("Value should be a collection.").toString());
-            }
-            if (valueType != null) {
-                final UML valueUML = valueType.getAnnotation(UML.class);
-                if (valueUML != null) {
-                    String expected = info.typeName;
-                    String actual   = valueUML.identifier();
-                    expected = TYPE_EQUIVALENCES.getOrDefault(expected, expected);
-                    actual   = TYPE_EQUIVALENCES.getOrDefault(actual,   actual);
-                    if (!expected.equals(actual)) {
-                        if (PENDING_FUTURE_SIS_VERSION)  // Temporarily disabled because require GeoAPI modifications.
-                        throw new SchemaException(errorInClassMember(javaName)
-                                .append("Declared value type: ").append(actual).append(System.lineSeparator())
-                                .append("Expected value type: ").append(expected).toString());
-                    }
-                }
-            }
-            /*
-             * Verify if we have a @XmlNs for the type of the value. This is probably not required, but we
-             * do that as a safety. A common namespace added by this check is Metadata Common Classes (MCC).
-             */
-            final Map<String, SchemaCompliance.Element> valueInfo = schemas.getTypeDefinition(info.typeName);
-            if (valueInfo != null) {
-                final SchemaInformation.Element typeAndNS = valueInfo.get(null);
-                if (typeAndNS != null) {
-                    final String valueNS = typeAndNS.namespace;
-                    if (namespaceIsUsed.put(valueNS, Boolean.TRUE) == null) {
-                        throw new SchemaException(errorInClassMember(javaName)
-                                .append("Missing @XmlNs for property value namespace: ").append(valueNS).toString());
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns a message beginning with "Error in …", to be completed by the caller.
-     * This is an helper method for exception messages.
-     *
-     * @param  name  the property name, or {@code null} if none.
-     */
-    private StringBuilder errorInClassMember(final String name) {
-        final StringBuilder builder = new StringBuilder(80).append("Error in ");
-        if (isDeprecatedClass) {
-            builder.append("legacy ");
-        }
-        builder.append(currentClass.getCanonicalName());
-        if (name != null) {
-            builder.append('.').append(name);
-        }
-        return builder.append(':').append(System.lineSeparator());
-    }
-}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java
deleted file mode 100644
index 35cfbaa..0000000
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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.test.xml;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Files;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryIteratorException;
-import java.util.Map;
-import java.util.HashMap;
-import javax.xml.bind.annotation.XmlNs;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.parsers.ParserConfigurationException;
-import org.xml.sax.SAXException;
-import org.opengis.geoapi.Departures;
-import org.opengis.geoapi.DocumentationStyle;
-import org.opengis.geoapi.SchemaInformation;
-import org.opengis.geoapi.SchemaException;
-import org.apache.sis.util.StringBuilders;
-
-
-/**
- * Compares JAXB annotations against the ISO 19115 schemas. This test requires a connection to
- * <a href="https://standards.iso.org/iso/19115/-3/">https://standards.iso.org/iso/19115/-3/</a>.
- * All classes in a given directory are scanned.
- *
- * <h2>Limitations</h2>
- * Current implementation ignores the XML prefix (e.g. {@code "cit:"} in {@code "cit:CI_Citation"}).
- * We assume that there is no name collision, especially given that {@code "CI_"} prefix in front of
- * most OGC/ISO class names have the effect of a namespace. If a collision nevertheless happen, then
- * an exception will be thrown.
- *
- * <p>Current implementation assumes that XML element name, type name, property name and property type
- * name follow some naming convention. For example type names are suffixed with {@code "_Type"} in OGC
- * schemas, while property type names are suffixed with {@code "_PropertyType"}.  This class throws an
- * exception if a type does not follow the expected naming convention. This requirement makes
- * implementation easier, by reducing the amount of {@link Map}s that we need to manage.</p>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final strictfp class SchemaCompliance extends SchemaInformation {
-    /**
-     * The prefix of XML type names for properties. In ISO/OGC schemas, this prefix does not appear
-     * in the definition of class types but may appear in the definition of property types.
-     */
-    private static final String ABSTRACT_PREFIX = "Abstract_";
-
-    /**
-     * The suffix of XML type names for classes.
-     * This is used by convention in OGC/ISO standards (but not necessarily in other XSD).
-     */
-    static final String TYPE_SUFFIX = "_Type";
-
-    /**
-     * Separator between XML prefix and the actual name.
-     */
-    private static final char PREFIX_SEPARATOR = ':';
-
-    /**
-     * Root directory from which to search for classes.
-     */
-    private final Path classRootDirectory;
-
-    /**
-     * The namespaces associated to prefixes, as declared by JAXB {@link XmlNs} annotations.
-     * Used for verifying that no prefix is defined twice for different namespaces.
-     *
-     * <p>This field is not really related to schema loading process. But we keep it in this class for
-     * {@link PackageVerifier} convenience, as a way to share a single map for all verifier instances.</p>
-     */
-    final Map<String,String> allXmlNS;
-
-    /**
-     * Creates a new verifier for classes under the given directory. The given directory shall be the
-     * root of {@code "*.class"} files. For example if the {@code mypackage.MyClass} class is compiled
-     * in the {@code "MyProject/target/classes/mypackage/MyClass.class"} file, then the root directory
-     * shall be {@code "MyProject/target/classes/"}.
-     *
-     * @param  classRootDirectory   the root of compiled class files.
-     * @param  schemaRootDirectory  if the computer contains a local copy of ISO schemas, path to that directory.
-     *                              Otherwise {@code null}. This is only for making tests faster.
-     */
-    public SchemaCompliance(final Path classRootDirectory, final Path schemaRootDirectory) {
-        super(schemaRootDirectory, new Departures(), DocumentationStyle.NONE);
-        this.classRootDirectory = classRootDirectory;
-        allXmlNS = new HashMap<>();
-    }
-
-    /**
-     * Verifies {@link XmlElement} annotations on all {@code *.class} files in the given directory and sub-directories.
-     * The given directory must be a sub-directory of the root directory given at construction time.
-     * This method will invoke itself for scanning sub-directories.
-     *
-     * @param  directory  the directory to scan for classes, relative to class root directory.
-     * @throws IOException if an error occurred while reading files or schemas.
-     * @throws ClassNotFoundException if an error occurred while loading a {@code "*.class"} file.
-     * @throws ParserConfigurationException if {@link javax.xml.parsers.DocumentBuilder} can not be created.
-     * @throws SAXException if an error occurred while parsing the XSD file.
-     * @throws SchemaException if a XSD file does not comply with our assumptions,
-     *         or a JAXB annotation failed a compliance check.
-     */
-    public void verify(final Path directory)
-            throws IOException, ClassNotFoundException, ParserConfigurationException, SAXException, SchemaException
-    {
-        PackageVerifier verifier = null;
-        final StringBuilder buffer = new StringBuilder();
-        try (DirectoryStream<Path> stream = Files.newDirectoryStream(classRootDirectory.resolve(directory))) {
-            for (Path path : stream) {
-                final String filename = path.getFileName().toString();
-                if (!filename.startsWith(".")) {
-                    if (Files.isDirectory(path)) {
-                        verify(path);
-                    } else if (filename.endsWith(".class")) {
-                        path = classRootDirectory.relativize(path);
-                        buffer.setLength(0);
-                        buffer.append(path.toString()).setLength(buffer.length() - 6);      // Remove ".class" suffix.
-                        StringBuilders.replace(buffer, '/', '.');
-                        final Class<?> c = Class.forName(buffer.toString());
-                        if (verifier == null) {
-                            verifier = new PackageVerifier(this, c.getPackage());
-                        }
-                        verifier.verify(c);
-                    }
-                }
-            }
-        } catch (DirectoryIteratorException e) {
-            throw e.getCause();
-        }
-    }
-
-    /**
-     * Verifies that the relationship between the name of the given entity and its type are consistent with
-     * OGC/ISO conventions. This method ignores the prefix (e.g. {@code "mdb:"} in {@code "mdb:MD_Metadata"}).
-     *
-     * @param  enclosing  schema or other container where the error happened.
-     * @param  name       the class or property name. Example: {@code "MD_Metadata"}, {@code "citation"}.
-     * @param  type       the type of the above named object. Example: {@code "MD_Metadata_Type"}, {@code "CI_Citation_PropertyType"}.
-     * @param  suffix     the expected suffix at the end of {@code type}.
-     * @throws SchemaException if the given {@code name} and {@code type} are not compliant with expected convention.
-     */
-    static void verifyNamingConvention(final String enclosing,
-            final String name, final String type, final String suffix) throws SchemaException
-    {
-        if (type.endsWith(suffix)) {
-            int nameStart = name.indexOf(PREFIX_SEPARATOR) + 1;        // Skip "mdb:" or similar prefix.
-            int typeStart = type.indexOf(PREFIX_SEPARATOR) + 1;
-            if (name.startsWith(ABSTRACT_PREFIX, nameStart)) nameStart += ABSTRACT_PREFIX.length();
-            if (type.startsWith(ABSTRACT_PREFIX, typeStart)) typeStart += ABSTRACT_PREFIX.length();
-            final int length = name.length() - nameStart;
-            if (type.length() - typeStart - suffix.length() == length &&
-                    type.regionMatches(typeStart, name, nameStart, length))
-            {
-                return;
-            }
-        }
-        throw new SchemaException(String.format("Error in %s:%n" +
-                "The type name should be the name with \"%s\" suffix, but found name=\"%s\" and type=\"%s\">.",
-                enclosing, suffix, name, type));
-    }
-
-    /**
-     * Removes leading and trailing spaces if any, then the prefix and the suffix in the given name.
-     * The prefix is anything before the first {@value #PREFIX_SEPARATOR} character.
-     * The suffix must be the given string, otherwise an exception is thrown.
-     *
-     * @param  name     the name from which to remove prefix and suffix.
-     * @param  suffix   the suffix to remove.
-     * @return the given name without prefix and suffix.
-     * @throws SchemaException if the given name does not end with the given suffix.
-     */
-    static String trim(String name, final String suffix) throws SchemaException {
-        name = name.trim();
-        if (name.endsWith(suffix)) {
-            return name.substring(name.indexOf(PREFIX_SEPARATOR) + 1, name.length() - suffix.length());
-        }
-        throw new SchemaException(String.format("Expected a name ending with \"%s\" but got \"%s\".", suffix, name));
-    }
-}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java
index da318ef..75c031f 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java
@@ -18,7 +18,7 @@
 /**
  * Utility methods for testing XML files or JAXB annotations.
  * {@link org.apache.sis.test.xml.AnnotationConsistencyCheck} and
- * {@link org.apache.sis.test.xml.SchemaCompliance} verifies JAXB annotations.
+ * {@code SchemaCompliance} verifies JAXB annotations.
  * {@link org.apache.sis.test.xml.DocumentComparator} compares an actual XML document with the expected one.
  *
  * <p>Objects defined in this package are only for SIS testing purpose any many change
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/util/iso/DefaultRecordSchemaTest.java b/core/sis-metadata/src/test/java/org/apache/sis/util/iso/DefaultRecordSchemaTest.java
index 558ee4a..092d529 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/util/iso/DefaultRecordSchemaTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/util/iso/DefaultRecordSchemaTest.java
@@ -62,7 +62,7 @@
         assertSame("container", schema, recordType.getContainer());
         assertEquals("typeName", Names.createTypeName("MySchema", ":", "MyRecordType"), recordType.getTypeName());
         int count = 0;
-        for (final Map.Entry<MemberName,Type> entry : recordType.getFieldTypes().entrySet()) {
+        for (final Map.Entry<MemberName,Type> entry : recordType.getMemberTypes().entrySet()) {
             final String   expectedName;
             final String   expectedType;
             final Class<?> expectedClass;
@@ -108,7 +108,7 @@
         final DefaultRecordType copy = new DefaultRecordType(
                 recordType.getTypeName(),
                 recordType.getContainer(),
-                recordType.getFieldTypes());
+                recordType.getMemberTypes());
         assertEquals(recordType, copy);
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypesTest.java b/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypesTest.java
index 17161db..d077ca4 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypesTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypesTest.java
@@ -30,13 +30,13 @@
 import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.cs.AxisDirection;
-import org.opengis.parameter.ParameterDirection;
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.util.DefaultInternationalString;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.PENDING_NEXT_GEOAPI_RELEASE;
 
 
 /**
@@ -111,7 +111,7 @@
 
     /**
      * Tests the {@link Types#forEnumName(Class, String)} method with an enumeration from the JDK.
-     * Such enumerations do not implement the {@link org.opengis.util.ControlledVocabulary} interface.
+     * Such enumerations do not implement the {@code org.opengis.util.ControlledVocabulary} interface.
      *
      * @since 0.5
      */
@@ -125,21 +125,6 @@
     }
 
     /**
-     * Tests the {@link Types#forEnumName(Class, String)} method with an enumeration from GeoAPI.
-     * Such enumerations implement the {@link org.opengis.util.ControlledVocabulary} interface.
-     *
-     * @since 0.5
-     */
-    @Test
-    public void testForGeoapiEnumName() {
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "IN_OUT"));
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "INOUT"));
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "in out"));
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "in/out"));
-        assertNull(Types.forEnumName(ParameterDirection.class, "out/in"));
-    }
-
-    /**
      * Tests the {@link Types#forCodeName(Class, String, boolean)} method.
      */
     @Test
@@ -154,8 +139,11 @@
         assertSame(PixelInCell.CELL_CORNER, Types.forCodeName(PixelInCell.class, "cellCorner",  false));
         assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cell center", false));
         assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cellCenter",  false));
-        assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cell centre", false));
-        assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cellCentre",  false));
+
+        if (PENDING_NEXT_GEOAPI_RELEASE) {
+            assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cell centre", false));
+            assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cellCentre",  false));
+        }
     }
 
     /**
@@ -193,7 +181,7 @@
     }
 
     /**
-     * Tests the {@link Types#getDescription(ControlledVocabulary)} method.
+     * Tests the {@code Types.getDescription(ControlledVocabulary)} method.
      */
     @Test
     public void testGetCodeDescription() {
@@ -207,29 +195,27 @@
     }
 
     /**
-     * Tests the examples given in {@link Types#getListName(ControlledVocabulary)} javadoc.
+     * Tests the examples given in {@code Types.getListName(ControlledVocabulary)} javadoc.
      */
     @Test
     public void testGetListName() {
-        assertEquals("SV_ParameterDirection",   Types.getListName(ParameterDirection.IN_OUT));
         assertEquals("CS_AxisDirection",        Types.getListName(AxisDirection     .NORTH));
         assertEquals("CI_OnLineFunctionCode",   Types.getListName(OnLineFunction    .DOWNLOAD));
         assertEquals("MD_ImagingConditionCode", Types.getListName(ImagingCondition  .BLURRED_IMAGE));
     }
 
     /**
-     * Tests the examples given in {@link Types#getCodeName(ControlledVocabulary)} javadoc.
+     * Tests the examples given in {@code Types.getCodeName(ControlledVocabulary)} javadoc.
      */
     @Test
     public void testGetCodeName() {
-        assertEquals("in/out",       Types.getCodeName(ParameterDirection.IN_OUT));
         assertEquals("north",        Types.getCodeName(AxisDirection     .NORTH));
         assertEquals("download",     Types.getCodeName(OnLineFunction    .DOWNLOAD));
         assertEquals("blurredImage", Types.getCodeName(ImagingCondition  .BLURRED_IMAGE));
     }
 
     /**
-     * Tests the examples given in {@link Types#getCodeLabel(ControlledVocabulary)} javadoc.
+     * Tests the examples given in {@code Types.getCodeLabel(ControlledVocabulary)} javadoc.
      */
     @Test
     public void testGetCodeLabel() {
@@ -239,7 +225,7 @@
     }
 
     /**
-     * Tests {@link Types#getCodeTitle(ControlledVocabulary)}.
+     * Tests {@code Types.getCodeTitle(ControlledVocabulary)}.
      * Also opportunistically tests {@link Types#forCodeTitle(CharSequence)}.
      */
     @Test
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java b/core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java
index 3d02984..bf4d43a 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java
@@ -18,10 +18,10 @@
 
 import javax.xml.bind.JAXBException;
 import org.opengis.metadata.citation.Address;
-import org.opengis.metadata.Identifier;
 import org.opengis.metadata.acquisition.Instrument;
 import org.opengis.metadata.identification.DataIdentification;
 import org.opengis.metadata.identification.InitiativeType;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.internal.jaxb.metadata.replace.ReferenceSystemMetadata;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.metadata.SensorType;
@@ -32,6 +32,9 @@
 
 import static org.apache.sis.test.MetadataAssert.*;
 
+// Branch-dependent imports
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Tests the XML marshalling of {@code Anchor} and {@code CodeList} as substitution of {@code <gco:CharacterSequence>}.
@@ -71,7 +74,7 @@
                 "</gmd:MD_ReferenceSystem>";
 
         final ReferenceSystemMetadata md = unmarshal(ReferenceSystemMetadata.class, expected);
-        final Identifier id = md.getName();
+        final ReferenceIdentifier id = md.getName();
         assertEquals("codespace", "L101", id.getCodeSpace());
         assertEquals("code", "EPSG:4326", id.getCode());
     }
@@ -98,7 +101,7 @@
                 "  </mcc:codeSpace>\n" +
                 "</mcc:MD_Identifier>";
 
-        final Identifier id = unmarshal(Identifier.class, expected);
+        final DefaultIdentifier id = unmarshal(DefaultIdentifier.class, expected);
         assertEquals("codespace", "L101", id.getCodeSpace());
         assertEquals("code", "EPSG:4326", id.getCode());
     }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java b/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java
index ce3e27f..e2c09a1 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java
@@ -19,7 +19,7 @@
 import java.net.URISyntaxException;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.ArraysExt;
@@ -288,7 +288,7 @@
         assertTrue (c.equals(e2, ComparisonMode.DEBUG));
 
         // Following object should alway be different because it does not implement the same interface.
-        final Responsibility r1 = NilReason.TEMPLATE.createNilObject(Responsibility.class);
+        final ResponsibleParty r1 = NilReason.TEMPLATE.createNilObject(ResponsibleParty.class);
         assertFalse(c.equals(r1, ComparisonMode.STRICT));
         assertFalse(c.equals(r1, ComparisonMode.BY_CONTRACT));
         assertFalse(c.equals(r1, ComparisonMode.IGNORE_METADATA));
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/xml/RenameListGenerator.java b/core/sis-metadata/src/test/java/org/apache/sis/xml/RenameListGenerator.java
deleted file mode 100644
index 3381457..0000000
--- a/core/sis-metadata/src/test/java/org/apache/sis/xml/RenameListGenerator.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * 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.xml;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.nio.file.Path;
-import java.nio.file.Files;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryIteratorException;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Collections;
-import java.lang.reflect.Method;
-import javax.xml.bind.annotation.XmlSchema;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import org.opengis.geoapi.SchemaException;
-import org.apache.sis.internal.xml.LegacyNamespaces;
-
-
-/**
- * Creates a file in the {@value TransformingReader#FILENAME} format. This class can be executed if the content
- * has changed, or for verifying the current file. Output format contains namespaces first, then classes,
- * then properties. Example:
- *
- * {@preformat text
- * http://standards.iso.org/iso/19115/-3/cit/1.0
- *   CI_Address
- *     administrativeArea
- *     city
- *   CI_Citation
- *     citedResponsibleParty
- * }
- *
- * This class can be used as a starting point for generating a new file from scratch.
- * It should not be used for updating the existing file (unless a lot of things have changed)
- * because some of {@value TransformingReader#FILENAME} content have been edited by hand.
- * For generating a new file:
- *
- * {@preformat java
- *     public static void main(String[] args) throws Exception {
- *         RenameListGenerator gen = new RenameListGenerator(Paths.get("/path/to/your/classes"));
- *         gen.add(Paths.get("root/package/of/classes/to/add"));
- *         try (final BufferedWriter out = Files.newBufferedWriter(Paths.get("MyOutputFile.lst"))) {
- *             gen.print(out);
- *         }
- *     }
- * }
- */
-public final class RenameListGenerator {
-    /**
-     * Properties in those namespaces do not have older namespaces to map from.
-     */
-    private static final Set<String> LEGACY_NAMESPACES = Collections.unmodifiableSet(new HashSet<>(
-            Arrays.asList(LegacyNamespaces.GMD, LegacyNamespaces.GMI, LegacyNamespaces.SRV)));
-
-    /**
-     * The {@value} string used in JAXB annotations for default names or namespaces.
-     */
-    private static final String DEFAULT = "##default";
-
-    /**
-     * Root directory from which to search for classes.
-     */
-    private final Path classRootDirectory;
-
-    /**
-     * The content to write. Keys in the first (outer) map are namespaces. Keys in the enclosed maps
-     * are class names. Keys in the enclosed set are property names.
-     */
-    private final Map<String, Map<String, Set<String>>> content;
-
-    /**
-     * Creates a new {@value TransformingReader#FILENAME} generator for classes under the given directory.
-     * The given directory shall be the root of {@code "*.class"} files.
-     *
-     * @param  classRootDirectory   the root of compiled class files.
-     */
-    public RenameListGenerator(final Path classRootDirectory) {
-        this.classRootDirectory = classRootDirectory;
-        content = new TreeMap<>();
-    }
-
-    /**
-     * Gets the namespaces, types and properties for all class files in the given directory and sub-directories.
-     * Those information are memorized for future listing with {@link #print(Appendable)}.
-     *
-     * @param  directory  the directory to scan for classes, relative to class root directory.
-     * @throws IOException if an error occurred while reading files or schemas.
-     * @throws ClassNotFoundException if an error occurred while loading a {@code "*.class"} file.
-     * @throws SchemaException if two properties have the same name in the same class and namespace.
-     */
-    public void add(final Path directory) throws IOException, ClassNotFoundException, SchemaException {
-        try (DirectoryStream<Path> stream = Files.newDirectoryStream(classRootDirectory.resolve(directory))) {
-            for (Path path : stream) {
-                final String filename = path.getFileName().toString();
-                if (!filename.startsWith(".")) {
-                    if (Files.isDirectory(path)) {
-                        add(path);
-                    } else if (filename.endsWith(".class")) {
-                        path = classRootDirectory.relativize(path);
-                        String classname = path.toString();
-                        classname = classname.substring(0, classname.length() - 6).replace('/', '.');
-                        add(Class.forName(classname));
-                    }
-                }
-            }
-        } catch (DirectoryIteratorException e) {
-            throw e.getCause();
-        }
-    }
-
-    /**
-     * Gets the namespaces, types and properties for the given class.
-     * Properties defined in super-classes will be copied as if they were declared in-line.
-     * Those information are memorized for future listing with {@link #print(Appendable)}.
-     *
-     * @throws SchemaException if two properties have the same name in the same class and namespace.
-     */
-    private void add(Class<?> classe) throws SchemaException {
-        XmlRootElement root = classe.getDeclaredAnnotation(XmlRootElement.class);
-        if (root != null) {
-            /*
-             * Add the following entry:
-             *
-             *     http://a.namespace
-             *      PX_AClass
-             *       …
-             *
-             * Then list all properties below "PX_AClass". Note that the namespace may change because properties
-             * may be declared in different namespaces, but the class name stay the same. If the same properties
-             * are inherited by many classes, they will be repeated in each subclass.
-             */
-            final String topLevelTypeName = root.name();
-            String classNS = namespace(classe, root.namespace());
-            for (;; classNS = namespace(classe, root.namespace())) {
-                for (final Method method : classe.getDeclaredMethods()) {
-                    if (!method.isBridge()) {
-                        final XmlElement xe = method.getDeclaredAnnotation(XmlElement.class);
-                        if (xe != null) {
-                            String namespace = xe.namespace();
-                            if (namespace.equals(DEFAULT)) {
-                                namespace = classNS;
-                            }
-                            add(namespace, topLevelTypeName, xe.name());
-                        }
-                    }
-                }
-                classe = classe.getSuperclass();
-                root = classe.getDeclaredAnnotation(XmlRootElement.class);
-                if (root == null) break;
-            }
-        } else {
-            /*
-             * In Apache SIS implementation, classes without JAXB annotation except on a single method are
-             * code lists or enumerations. Those classes have exactly one method annotated with @XmlElement,
-             * and that method actually gives a type, not a property (because of the way OGC/ISO wrap every
-             * properties in a type).
-             */
-            XmlElement singleton = null;
-            for (final Method method : classe.getDeclaredMethods()) {
-                final XmlElement xe = method.getDeclaredAnnotation(XmlElement.class);
-                if (xe != null) {
-                    if (singleton != null) return;
-                    singleton = xe;
-                }
-            }
-        }
-    }
-
-    private static String namespace(final Class<?> classe, String classNS) {
-        if (classNS.equals(DEFAULT)) {
-            classNS = classe.getPackage().getDeclaredAnnotation(XmlSchema.class).namespace();
-        }
-        return classNS;
-    }
-
-    /**
-     * Adds a property in the given class in the given namespace.
-     */
-    private void add(final String namespace, final String typeName, final String property) throws SchemaException {
-        if (!LEGACY_NAMESPACES.contains(namespace)) {
-            if (!content.computeIfAbsent(namespace, (k) -> new TreeMap<>())
-                        .computeIfAbsent(typeName,  (k) -> new TreeSet<>())
-                        .add(property))
-            {
-                if (typeName.equals("Integer")) return;     // Exception because of GO_Integer and GO_Integer64.
-                throw new SchemaException(String.format("Duplicated property %s.%s in:%n%s", typeName, property, namespace));
-            }
-        }
-    }
-
-    /**
-     * Prints the {@value TransformingReader#FILENAME} file.
-     *
-     * @param  out  where to print the content.
-     * @throws IOException if an error occurred while printing the content.
-     */
-    public void print(final Appendable out) throws IOException {
-        for (final Map.Entry<String, Map<String, Set<String>>> e : content.entrySet()) {
-            out.append(e.getKey()).append('\n');                                            // Namespace
-            for (final Map.Entry<String, Set<String>> c : e.getValue().entrySet()) {
-                out.append(' ').append(c.getKey()).append('\n');                            // Class
-                for (final String p : c.getValue()) {
-                    out.append("  ").append(p).append('\n');                                // Property
-                }
-            }
-        }
-    }
-}
diff --git a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/iso/api-changes.properties b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/iso/api-changes.properties
index 8476d40..916a754 100644
--- a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/iso/api-changes.properties
+++ b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/iso/api-changes.properties
@@ -22,3 +22,36 @@
 # with the changes in the international standard. The UML identifiers of added methods are "number"
 # and "numberType" respectively.
 #
+org.opengis.metadata.citation.Citation=-getCollectiveTitle +getGraphics:graphic +getOnlineResources:onlineResource
+org.opengis.metadata.citation.Contact=-getAddress +getAddresses:address +getContactType:contactType -getOnlineResource +getOnlineResources:onlineResource -getPhone +getPhones:phone
+org.opengis.metadata.citation.OnlineResource=+getProtocolRequest:protocolRequest
+org.opengis.metadata.citation.ResponsibleParty=-getContactInfo -getIndividualName -getOrganisationName -getPositionName
+org.opengis.metadata.citation.Telephone=-getFacsimiles +getNumber:number +getNumberType:numberType -getVoices
+org.opengis.metadata.constraint.Constraints=+getConstraintApplicationScope:constraintApplicationScope +getGraphics:graphic +getReferences:reference +getReleasability:releasability +getResponsibleParties:responsibleParty
+org.opengis.metadata.content.Band=+getBoundMax:boundMax +getBoundMin:boundMin +getBoundUnits:boundUnits
+org.opengis.metadata.content.CoverageDescription=+getAttributeGroups:attributeGroup -getContentType -getDimensions +getProcessingLevelCode:processingLevelCode
+org.opengis.metadata.content.FeatureCatalogueDescription=+getFeatureTypeInfo:featureTypes -getFeatureTypes -getLanguages +getLocalesAndCharsets:locale
+org.opengis.metadata.content.RangeDimension=+getDescription:description -getDescriptor +getNames:name
+org.opengis.metadata.distribution.DigitalTransferOptions=+getDistributionFormats:distributionFormat -getOffLine +getOffLines:offLine +getTransferFrequency:transferFrequency
+org.opengis.metadata.distribution.Distribution=+getDescription:description
+org.opengis.metadata.distribution.Format=+getFormatSpecificationCitation:formatSpecificationCitation +getMedia:medium -getName -getSpecification -getVersion
+org.opengis.metadata.distribution.Medium=-getDensities +getDensity:density +getIdentifier:identifier
+org.opengis.metadata.distribution.StandardOrderProcess=+getOrderOptionsType:orderOptionsType +getOrderOptions:orderOptions
+org.opengis.metadata.ExtendedElementInformation=-getDomainCode +getRationale:rationale -getRationales -getShortName
+org.opengis.metadata.extent.SpatialTemporalExtent=+getVerticalExtent:verticalExtent
+org.opengis.metadata.identification.AggregateInformation=-getAggregateDataSetIdentifier -getAggregateDataSetName +getMetadataReference:metadataReference +getName:name
+org.opengis.metadata.identification.BrowseGraphic=+getImageConstraints:imageContraints +getLinkages:linkage
+org.opengis.metadata.identification.DataIdentification=-getCharacterSets -getLanguages +getLocalesAndCharsets:defaultLocale+otherLocale
+org.opengis.metadata.identification.Identification=+getAdditionalDocumentations:additionalDocumentation -getAggregationInfo +getAssociatedResources:associatedResource +getExtents:extent +getProcessingLevel:processingLevel +getSpatialRepresentationTypes:spatialRepresentationType +getSpatialResolutions:spatialResolution ~+getTemporalResolutions:temporalResolution +getTopicCategories:topicCategory
+org.opengis.metadata.identification.Keywords=+getKeywordClass:keywordClass
+org.opengis.metadata.identification.Resolution=+getAngularDistance:angularDistance +getLevelOfDetail:levelOfDetail +getVertical:vertical
+org.opengis.metadata.identification.ServiceIdentification=+getAccessProperties:accessProperties +getContainsChain:containsChain +getContainsOperations:containsOperations +getCoupledResources:coupledResource +getCouplingType:couplingType +getOperatedDatasets:operatedDataset +getOperatesOn:operatesOn +getProfiles:profile +getServiceStandards:serviceStandard +getServiceType:serviceType +getServiceTypeVersions:serviceTypeVersion
+org.opengis.metadata.identification.Usage=+getAdditionalDocumentation:additionalDocumentation +getIdentifiedIssues:identifiedIssues +getResponses:response
+org.opengis.metadata.Identifier=+getCodeSpace:codeSpace +getDescription:description +getVersion:version
+org.opengis.metadata.lineage.Lineage=+getAdditionalDocumentation:additionalDocumentation +getScope:scope
+org.opengis.metadata.lineage.ProcessStep=-getDate +getReferences:reference +getScope:scope
+org.opengis.metadata.lineage.Source=-getScaleDenominator +getScope:scope -getSourceExtents +getSourceMetadata:sourceMetadata +getSourceSpatialResolution:sourceSpatialResolution
+org.opengis.metadata.maintenance.MaintenanceInformation=-getDateOfNextUpdate +getMaintenanceDates:maintenanceDate +getMaintenanceScopes:maintenanceScope -getUpdateScopeDescriptions -getUpdateScopes
+org.opengis.metadata.Metadata=+getAlternativeMetadataReferences:alternativeMetadataReference -getCharacterSet -getCharacterSets -getDataSetUri -getDateStamp +getDateInfo:dateInfo -getFileIdentifier -getHierarchyLevelNames -getHierarchyLevels -getLanguage -getLanguages -getLocales +getLocalesAndCharsets:defaultLocale+otherLocale +getMetadataIdentifier:metadataIdentifier +getMetadataLinkages:metadataLinkage +getMetadataProfiles:metadataProfile +getMetadataScopes:metadataScope -getMetadataStandardName -getMetadataStandardVersion +getMetadataStandards:metadataStandard -getParentIdentifier +getParentMetadata:parentMetadata +getResourceLineages:resourceLineage
+org.opengis.metadata.quality.Scope=-getExtent +getExtents:extent
+org.opengis.metadata.spatial.Dimension=+getDimensionDescription:dimensionDescription +getDimensionTitle:dimensionTitle
diff --git a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml
index 209225e..850ac3f 100644
--- a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml
+++ b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml
@@ -76,9 +76,6 @@
               </gco:TypeName>
             </gco:attributeType>
           </srv:name>
-          <srv:direction>
-            <srv:SV_ParameterDirection>in</srv:SV_ParameterDirection>
-          </srv:direction>
           <srv:optionality>
             <gco:CharacterString>Optional</gco:CharacterString>
           </srv:optionality>
diff --git a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml
index 8dcd11d..fbb85ab 100644
--- a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml
+++ b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml
@@ -41,7 +41,7 @@
   </srv:serviceTypeVersion>
 
   <srv:couplingType>
-    <srv:SV_CouplingType codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#SV_CouplingType" codeListValue="loose" codeSpace="eng">Loose</srv:SV_CouplingType>
+    <srv:SV_CouplingType codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#SV_CouplingType" codeListValue="loose">Loose</srv:SV_CouplingType>
   </srv:couplingType>
 
   <srv:coupledResource>
@@ -62,7 +62,7 @@
             <gco:CharacterString>Get Map</gco:CharacterString>
           </srv:operationName>
           <srv:distributedComputingPlatform>
-            <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices" codeSpace="eng">Web services</srv:DCPList>
+            <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices">Web services</srv:DCPList>
           </srv:distributedComputingPlatform>
           <srv:connectPoint gco:nilReason="missing"/>
           <srv:parameter>
@@ -81,9 +81,6 @@
                   </gco:attributeType>
                 </gco:MemberName>
               </srv:name>
-              <srv:direction>
-                <srv:SV_ParameterDirection>in</srv:SV_ParameterDirection>
-              </srv:direction>
               <srv:optionality>
                 <gco:Boolean>true</gco:Boolean>
               </srv:optionality>
@@ -103,7 +100,7 @@
         <gco:CharacterString>Get Map</gco:CharacterString>
       </srv:operationName>
       <srv:distributedComputingPlatform>
-        <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices" codeSpace="eng">Web services</srv:DCPList>
+        <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices">Web services</srv:DCPList>
       </srv:distributedComputingPlatform>
       <srv:connectPoint gco:nilReason="missing"/>
       <srv:parameter>
@@ -122,9 +119,6 @@
               </gco:attributeType>
             </gco:MemberName>
           </srv:name>
-          <srv:direction>
-            <srv:SV_ParameterDirection>in</srv:SV_ParameterDirection>
-          </srv:direction>
           <srv:optionality>
             <gco:Boolean>true</gco:Boolean>
           </srv:optionality>
diff --git a/core/sis-portrayal/pom.xml b/core/sis-portrayal/pom.xml
index b7ad53a..63ae284 100644
--- a/core/sis-portrayal/pom.xml
+++ b/core/sis-portrayal/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
@@ -113,11 +113,6 @@
       <artifactId>sis-storage</artifactId>
       <version>${project.version}</version>
     </dependency>
-    <dependency>
-      <groupId>org.locationtech.jts</groupId>
-      <artifactId>jts-core</artifactId>
-      <optional>true</optional>
-    </dependency>
 
     <!-- Test dependencies -->
     <dependency>
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ExceptionPresentation.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ExceptionPresentation.java
deleted file mode 100644
index 2a9d447..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ExceptionPresentation.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.map;
-
-import org.apache.sis.portrayal.MapLayer;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.util.ArgumentChecks;
-import org.opengis.feature.Feature;
-
-
-/**
- * Produced by the portrayal engines when an exception occurred.
- * Exception presentations are placed in the Stream of presentation leaving
- * the user the choice to log, ignore or stop rendering as needed.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-public class ExceptionPresentation extends Presentation {
-
-    private final Exception exception;
-
-    /**
-     * @param exception not null.
-     */
-    public ExceptionPresentation(Exception exception) {
-        ArgumentChecks.ensureNonNull("exception", exception);
-        this.exception = exception;
-    }
-
-    /**
-     * @param exception not null.
-     */
-    public ExceptionPresentation(MapLayer layer, Resource resource, Feature candidate, Exception exception) {
-        super(layer,resource, candidate);
-        ArgumentChecks.ensureNonNull("exception", exception);
-        this.exception = exception;
-    }
-
-    /**
-     * @return exception, never null
-     */
-    public Exception getException() {
-        return exception;
-    }
-}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ListChangeEvent.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ListChangeEvent.java
deleted file mode 100644
index 7853b63..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ListChangeEvent.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.map;
-
-import java.beans.PropertyChangeEvent;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import org.apache.sis.measure.NumberRange;
-
-/**
- * Event generated by modified list properties.
- *
- * @author Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- */
-public final class ListChangeEvent<T> extends PropertyChangeEvent {
-
-    public enum Type {
-        ADDED,
-        REMOVED,
-        CHANGED
-    }
-
-    private final NumberRange<Integer> range;
-    private final Type type;
-    private final List<T> items;
-
-    private ListChangeEvent(final Object source, String propertyName, List<T> originalList, final List<? extends T> items, final NumberRange<Integer> range, final Type type){
-        super(source, propertyName, originalList, originalList);
-        this.range = range;
-        this.type = type;
-        this.items = (items != null) ? Collections.unmodifiableList(new ArrayList<>(items)) : null;
-    }
-
-    /**
-     * Returns the range index of the affected items.
-     * If the event type is Added, the range correspond to the index range after insertion.
-     * If the event type is Removed, the range correspond to the index before deletion.
-     * @return NumberRange added or removed range.
-     */
-    public NumberRange<Integer> getRange(){
-        return range;
-    }
-
-    /**
-     * Returns event type.
-     */
-    public Type getType(){
-        return type;
-    }
-
-    /**
-     * Returns the affected items of this event.
-     * This property is set if event is of type added or removed.
-     *
-     * @return List
-     */
-    public List<T> getItems(){
-        return items;
-    }
-
-    public static <T> ListChangeEvent<T> added(Object source, String propertyName, List<T> originalList, T newItem, final int index) {
-        return added(source, propertyName, originalList, Arrays.asList(newItem),
-                NumberRange.create(index, true, index, true));
-    }
-
-    public static <T> ListChangeEvent<T> added(Object source, String propertyName, List<T> originalList, List<T> newItems, final NumberRange<Integer> range) {
-        return new ListChangeEvent<>(source, propertyName, originalList,  newItems, range, Type.ADDED);
-    }
-
-    public static <T> ListChangeEvent<T> removed(Object source, String propertyName, List<T> originalList, T newItem, final int index) {
-        return removed(source, propertyName, originalList, Arrays.asList(newItem),
-                NumberRange.create(index, true, index, true));
-    }
-
-    public static <T> ListChangeEvent<T> removed(Object source, String propertyName, List<T> originalList, List<T> oldItems, final NumberRange<Integer> range) {
-        return new ListChangeEvent<>(source, propertyName, originalList, oldItems, range, Type.REMOVED);
-    }
-
-    public static <T> ListChangeEvent<T> changed(Object source, String propertyName, List<T> originalList) {
-        return new ListChangeEvent<>(source, propertyName, originalList, null, null, Type.CHANGED);
-    }
-}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/NotifiedList.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/NotifiedList.java
deleted file mode 100644
index 0677f13..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/NotifiedList.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.map;
-
-import java.util.AbstractList;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import org.apache.sis.measure.NumberRange;
-
-/**
- * Decorate a CopyOnWriteArrayList and notify changes when elements are added or removed.
- *
- * @author Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- */
-public abstract class NotifiedList<T> extends AbstractList<T> {
-
-    private final CopyOnWriteArrayList<T> parent = new CopyOnWriteArrayList<>();
-
-    @Override
-    public T get(int index) {
-        return parent.get(index);
-    }
-
-    @Override
-    public int size() {
-        return parent.size();
-    }
-
-    @Override
-    public T set(int index, T element) {
-        final T old = parent.set(index, element);
-        notifyReplace(old, element, index);
-        return old;
-    }
-
-    @Override
-    public void add(int index, T element) {
-        parent.add(index, element);
-        notifyAdd(element, index);
-    }
-
-    @Override
-    public T remove(int index) {
-        final T old = parent.remove(index);
-        notifyRemove(old, index);
-        return old;
-    }
-
-    protected abstract void notifyAdd(final T item, int index);
-
-    protected abstract void notifyAdd(final List<T> items, NumberRange<Integer> range);
-
-    protected abstract void notifyRemove(final T item, int index);
-
-    protected abstract void notifyRemove(final List<T> items, NumberRange<Integer> range);
-
-    protected abstract void notifyReplace(final T olditem, final T newitem, int index);
-}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java
deleted file mode 100644
index 580abc5..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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.map;
-
-import java.util.Objects;
-import org.apache.sis.coverage.grid.GridCoverage;
-import org.apache.sis.portrayal.MapLayer;
-import org.apache.sis.storage.DataStore;
-import org.apache.sis.storage.Resource;
-import org.opengis.feature.Feature;
-
-
-/**
- * Parent class of all elements having a graphical representation on the map.
- * {@code Presentation} instances are organized in a tree closely related to the {@link MapLayer} tree.
- * The {@link MapLayer} tree specifies data and styles in a device-independent way and for all zoom levels.
- * The {@code Presentation} tree can be seen as {@link MapLayer} information filtered for the current rendering
- * context (map projection, zoom level, window size, <i>etc.</i>) and converted to data structures more directly
- * exploitable by the display device. In particular a {@code Presentation} object must encapsulate data without
- * costly evaluation, processing or loading work remaining to do: the {@link Feature} or the {@link GridCoverage}
- * (for instance) should have been read in advance from the {@link DataStore}.
- * The preparation of a {@link Presentation} tree before displaying may be done in a background thread.
- *
- * <p>Note that multiple presentations may be generated for the same feature.
- * Consequently many {@code Presentation} instances may encapsulate the same {@link Feature} instance.</p>
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @todo Consider renaming as {@code Graphic} for emphasis that this is a graphical representation of something.
- *       This would be consistent with legacy GO-1 specification (even if retired, it still have worthy material).
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-public abstract class Presentation {
-
-    private MapLayer layer;
-    private Resource resource;
-    private Feature candidate;
-
-    public Presentation() {
-    }
-
-    public Presentation(MapLayer layer, Resource resource, Feature candidate) {
-        this.layer = layer;
-        this.resource = resource;
-        this.candidate = candidate;
-    }
-
-    /**
-     * Returns the original map layer the feature comes from.
-     *
-     * @return MapLayer can be null if the presentation is not associated to a layer.
-     */
-    public MapLayer getLayer() {
-        return layer;
-    }
-
-    /**
-     * Set map layer this presentation comes from.
-     *
-     * @param layer may be null
-     */
-    public void setLayer(MapLayer layer) {
-        this.layer = layer;
-    }
-
-    public Resource getResource() {
-        return resource;
-    }
-
-    public void setResource(Resource resource) {
-        this.resource = resource;
-    }
-
-    /**
-     * Returns the original candidate having this presentation.
-     * This is often a Coverage or a Feature.
-     *
-     * @return can be null if the presentation is not associated to any identifiable object.
-     */
-    public Feature getCandidate() {
-        return candidate;
-    }
-
-    /**
-     * Set feature this presentation comes from.
-     *
-     * @param feature may be null
-     */
-    public void setCandidate(Feature feature) {
-        this.candidate = feature;
-    }
-
-    @Override
-    public int hashCode() {
-        int hash = 7;
-        hash = 89 * hash + Objects.hashCode(this.layer);
-        hash = 89 * hash + Objects.hashCode(this.resource);
-        hash = 89 * hash + Objects.hashCode(this.candidate);
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final Presentation other = (Presentation) obj;
-        if (!Objects.equals(this.layer, other.layer)) {
-            return false;
-        }
-        if (!Objects.equals(this.resource, other.resource)) {
-            return false;
-        }
-        if (!Objects.equals(this.candidate, other.candidate)) {
-            return false;
-        }
-        return true;
-    }
-
-}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PropertyNameCollector.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PropertyNameCollector.java
deleted file mode 100644
index 696ec0b..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PropertyNameCollector.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.map;
-
-import java.util.Set;
-import java.util.HashSet;
-import org.opengis.filter.ValueReference;
-
-
-/**
- * Collects all properties used in style elements.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-final class PropertyNameCollector extends SymbologyVisitor {
-    /**
-     * All value references found.
-     *
-     * @see ValueReference#getXPath()
-     */
-    final Set<String> references;
-
-    /**
-     * Creates a new collector.
-     */
-    PropertyNameCollector() {
-        references = new HashSet<>();
-    }
-
-    /**
-     * Invoked for each value reference found.
-     */
-    @Override
-    protected void visitProperty(final ValueReference<?,?> expression) {
-        if (expression != null) {
-            references.add(expression.getXPath());
-        }
-    }
-}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
deleted file mode 100644
index 029e365..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
+++ /dev/null
@@ -1,765 +0,0 @@
-/*
- * 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.map;
-
-import java.awt.geom.AffineTransform;
-import java.awt.geom.NoninvertibleTransformException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.BiFunction;
-import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.opengis.util.GenericName;
-import org.opengis.geometry.Envelope;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.referencing.datum.PixelInCell;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.feature.Features;
-import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.geometry.Envelopes;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.internal.feature.AttributeConvention;
-import org.apache.sis.storage.FeatureQuery;
-import org.apache.sis.portrayal.MapItem;
-import org.apache.sis.portrayal.MapLayer;
-import org.apache.sis.portrayal.MapLayers;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
-import org.apache.sis.storage.Aggregate;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.GridCoverageResource;
-import org.apache.sis.storage.Query;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.util.ArgumentChecks;
-
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.feature.PropertyType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.Expression;
-import org.opengis.style.FeatureTypeStyle;
-import org.opengis.style.Rule;
-import org.opengis.style.SemanticType;
-import org.opengis.style.Symbolizer;
-
-// Optional-dependencies (TODO: make library-independent)
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.MultiLineString;
-import org.locationtech.jts.geom.MultiPoint;
-import org.locationtech.jts.geom.MultiPolygon;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-
-
-/**
- * Generation a Stream of Presentation for a map.
- *
- * <p>
- * NOTE: this class is experimental and subject to modifications.
- * </p>
- *
- * <p>
- * Style properties ignored :
- * </p>
- * <ul>
- *   <li>Style : isDefault : behavior from ISO 19117 which can be replaced by OGC SE Rule.isElseRule</li>
- *   <li>Style : defaultSpecification : behavior from ISO 19117 which can be replaced by OGC SE Rule.isElseRule</li>
- *   <li>FeatureTypeStyle : instance ids : behavior from ISO 19117 which can be replaced by OGC SE Rule.filter</li>
- * </ul>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-public final class SEPortrayer {
-    /**
-     * Rule scale tolerance.
-     */
-    private static final double SE_EPSILON = 1e-6;
-
-    /**
-     * Used in SLD/SE to calculate scale for degree CRSs.
-     */
-    private static final double SE_DEGREE_TO_METERS = 6378137.0 * 2.0 * Math.PI / 360.0;
-    private static final double DEFAULT_DPI = 90.0; // ~ 0.28 * 0.28mm
-    private static final double PIXEL_SIZE = 0.0254;
-
-    /**
-     * A test to know if a given property is an SIS convention or not. Return true if
-     * the property is NOT marked as an SIS convention, false otherwise.
-     */
-    private static final Predicate<IdentifiedType> IS_NOT_CONVENTION = p -> !AttributeConvention.contains(p.getName());
-
-    private final FilterFactory<Feature,Object,Object> filterFactory;
-
-    /**
-     * Hint to avoid decimating feature properties because they may be used
-     * later for other purposes.
-     */
-    private boolean preserveProperties;
-
-    private BiFunction<GridGeometry, Symbolizer, Double> marginSolver;
-
-    public SEPortrayer() {
-        filterFactory = DefaultFilterFactory.forFeatures();
-        marginSolver  = (GridGeometry t, Symbolizer u) -> 30.0;
-    }
-
-    /**
-     * Hint to avoid decimating feature properties because they may be used
-     * later for other purposes.
-     * Default value is false.
-     *
-     * @return true if all feature properties are preserved in Presentation instances.
-     */
-    public boolean isPreserveProperties() {
-        return preserveProperties;
-    }
-
-    /**
-     * Hint to avoid decimating feature properties because they may be used
-     * later for other purposes.
-     * Default value is false.
-     *
-     * @param preserveProperties set to true to preserve all feature properties in Presentation instances.
-     */
-    public void setPreserveProperties(boolean preserveProperties) {
-        this.preserveProperties = preserveProperties;
-    }
-
-    /**
-     * Replace default margin solver.
-     * The margin solver try to guess the expected symbolizer size to expand the query bounding box.
-     * @param marginSolver
-     */
-    public void setMarginSolver(BiFunction<GridGeometry, Symbolizer, Double> marginSolver) {
-        ArgumentChecks.ensureNonNull("marginSolver", marginSolver);
-        this.marginSolver = marginSolver;
-    }
-
-    /**
-     * Generate presentations for the given map item.
-     */
-    public Stream<Presentation> present(GridGeometry canvas, final MapItem mapitem) {
-        Stream<Presentation> stream = Stream.empty();
-        if (mapitem.isVisible()) {
-            if (mapitem instanceof MapLayer) {
-                final MapLayer layer = (MapLayer) mapitem;
-                stream = Stream.concat(stream, present(canvas, layer, layer.getData()));
-            } else if (mapitem instanceof MapLayers) {
-                final MapLayers layers = (MapLayers) mapitem;
-                for (MapItem item : layers.getComponents()) {
-                    stream = Stream.concat(stream, present(canvas, item));
-                }
-            }
-        }
-        return stream;
-    }
-
-    private Stream<Presentation> present(GridGeometry canvas, MapLayer layer, Resource resource) {
-        final Resource refResource = resource;
-        Stream<Presentation> stream = Stream.empty();
-        final FeatureType type;
-        if (resource instanceof FeatureSet) {
-            // Apply user query if defined.
-            final Query basequery = layer.getQuery();
-            if (basequery != null) try {
-                resource = ((FeatureSet) resource).subset(basequery);
-            } catch (DataStoreException ex) {
-                stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-                return stream;
-            }
-            try {
-                type = ((FeatureSet) resource).getType();
-            } catch (DataStoreException ex) {
-                stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-                return stream;
-            }
-        } else if (resource instanceof GridCoverageResource) {
-
-            // Apply user query if defined.
-            final Query basequery = layer.getQuery();
-            if (basequery != null) try {
-                resource = ((GridCoverageResource) resource).subset(basequery);
-            } catch (DataStoreException ex) {
-                stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-                return stream;
-            }
-
-            type = null;
-        } else if (resource instanceof Aggregate) {
-            try {
-                // Combine each component resource in the stream.
-                for (final Resource r : ((Aggregate) resource).components()) {
-                    stream = Stream.concat(stream, present(canvas, layer, r));
-                }
-            } catch (DataStoreException ex) {
-                stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-            }
-            return stream;
-        } else {
-            // Unknown type.
-            return Stream.empty();
-        }
-        final MathTransform   gridToCRS = canvas.getGridToCRS(PixelInCell.CELL_CENTER);
-        final AffineTransform dispToObj;
-        final AffineTransform objToDisp;
-        try {
-            dispToObj = AffineTransforms2D.castOrCopy(gridToCRS);
-            objToDisp = dispToObj.createInverse();
-        } catch (IllegalArgumentException | NoninvertibleTransformException ex) {
-            stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-            return stream;
-        }
-        final double seScale = getSEScale(canvas, objToDisp);
-        for (FeatureTypeStyle fts : layer.getStyle().featureTypeStyles()) {
-            final List<Rule> rules = getValidRules(fts, seScale, type);
-            if (rules.isEmpty()) continue;
-
-            // Prepare the renderers.
-            final int elseRuleIndex = sortByElseRule(rules);
-            {   //special case for resource symbolizers
-                //resource symbolizers must be alone in a FTS
-                ResourceSymbolizer resourceSymbolizer = null;
-                int count = 0;
-                for (final Rule r : rules) {
-                    for (final Symbolizer s : r.symbolizers()) {
-                        count++;
-                        if (s instanceof ResourceSymbolizer) {
-                            resourceSymbolizer = (ResourceSymbolizer) s;
-                        }
-                    }
-                }
-                if (resourceSymbolizer != null) {
-                    if (count > 1) {
-                        Exception ex = new IllegalArgumentException("A resource symbolizer must be alone in a FeatureTypeStyle element." );
-                        final ExceptionPresentation presentation = new ExceptionPresentation(ex);
-                        presentation.setLayer(layer);
-                        presentation.setResource(resource);
-                        stream = Stream.concat(stream, Stream.of(presentation));
-                    } else {
-                        final SEPresentation presentation = new SEPresentation(layer, resource, null, resourceSymbolizer);
-                        stream = Stream.concat(stream, Stream.of(presentation));
-                    }
-                    continue;
-                }
-            }
-            // Extract the used names.
-            Set<String> names;
-            if (preserveProperties) {
-                names = null;
-            } else {
-                names = propertiesNames(rules);
-                if (names.contains("*")) {
-                    // We need all properties.
-                    names = null;
-                }
-            }
-            if (resource instanceof GridCoverageResource) {
-                boolean painted = false;
-                for (int i = 0; i < elseRuleIndex; i++) {
-                    final Stream<Presentation> subStream = present(rules.get(i), layer, resource, resource, null);
-                    if (subStream != null) {
-                        painted = true;
-                        stream = Stream.concat(stream, subStream);
-                    }
-                }
-                // The data hasn't been painted, paint it with the 'else' rules.
-                if (!painted) {
-                    for (int i = elseRuleIndex, n = rules.size(); i < n; i++) {
-                        final Stream<Presentation> subStream = present(rules.get(i), layer, resource, resource, null);
-                        if (subStream != null) {
-                            stream = Stream.concat(stream, subStream);
-                        }
-                    }
-                }
-            } else if (resource instanceof FeatureSet) {
-                final FeatureSet fs = (FeatureSet) resource;
-                // Calculate max symbol size, to expand search envelope.
-                double symbolsMargin = 0.0;
-                for (Rule rule : rules) {
-                    for (Symbolizer symbolizer : rule.symbolizers()) {
-                        symbolsMargin = Math.max(symbolsMargin, marginSolver.apply(canvas, symbolizer));
-                    }
-                }
-                if (Double.isNaN(symbolsMargin) || Double.isInfinite(symbolsMargin)) {
-                    // Symbol margin can not be pre calculated, expect a max of 300pixels.
-                    symbolsMargin = 300f;
-                }
-                if (symbolsMargin > 0) {
-                    symbolsMargin *= AffineTransforms2D.getScale(dispToObj);
-                }
-                try {
-                    // Optimize query.
-                    final Query query = prepareQuery(canvas, fs, names, rules, symbolsMargin);
-                    final Stream<Presentation> s = fs.subset(query)
-                            .features(false)
-                            .flatMap((Feature feature) ->
-                    {
-                        Stream<Presentation> stream1 = Stream.empty();
-                        boolean painted = false;
-                        for (int i = 0; i < elseRuleIndex; i++) {
-                            final Stream<Presentation> subStream = present(rules.get(i), layer, fs, refResource, feature);
-                            if (subStream != null) {
-                                painted = true;
-                                stream1 = Stream.concat(stream1, subStream);
-                            }
-                        }
-                        // The feature hasn't been painted, paint it with the 'else' rules.
-                        if (!painted) {
-                            for (int i = elseRuleIndex, n = rules.size(); i < n; i++) {
-                                final Stream<Presentation> subStream = present(rules.get(i), layer, fs, refResource, feature);
-                                if (subStream != null) {
-                                    stream1 = Stream.concat(stream1, subStream);
-                                }
-                            }
-                        }
-                        return stream1;
-                    });
-                    stream = Stream.concat(stream, s);
-                } catch (DataStoreException | TransformException ex) {
-                    stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
-                }
-            }
-        }
-        return stream;
-    }
-
-    private static Stream<Presentation> present(Rule rule, MapLayer layer,
-            Resource resource, Resource refResource, Feature feature)
-    {
-        final Filter ruleFilter = rule.getFilter();             // TODO: type.
-        //test if the rule is valid for this resource/feature
-        if (ruleFilter == null || ruleFilter.test(feature == null ? resource : feature)) {
-            Stream<Presentation> stream = Stream.empty();
-            for (final Symbolizer symbolizer : rule.symbolizers()) {
-                final SEPresentation presentation = new SEPresentation(layer, refResource, feature, symbolizer);
-                stream = Stream.concat(stream, Stream.of(presentation));
-            }
-            return stream;
-        }
-        return null;
-    }
-
-    /**
-     * Creates an optimal query to send to the Featureset, knowing which properties are knowned and
-     * the appropriate bounding box to filter.
-     */
-    private FeatureQuery prepareQuery(GridGeometry canvas, FeatureSet fs, Set<String> requiredProperties,
-            List<Rule> rules, double symbolsMargin) throws DataStoreException, TransformException
-    {
-        final FeatureQuery query = new FeatureQuery();
-        final FeatureType schema = fs.getType();
-        /*
-         * Check if some used properties are not part of the type.
-         * This means the FeatureSet may contain sub types.
-         * We can not optimize the query.
-         */
-        if (requiredProperties != null) {
-            for (String pn : requiredProperties) {
-                try {
-                    schema.getProperty(pn);
-                } catch (PropertyNotFoundException e) {
-                    return query;
-                }
-            }
-        }
-        // Search all geometry expression used in the symbols.
-        boolean allDefined = true;
-        final Set<Expression<Feature,?>> geomProperties = new HashSet<>();
-        if (rules != null) {
-            for (final Rule r : rules) {
-                for (final Symbolizer s : r.symbolizers()) {
-                    final Expression<Feature,?> expGeom = s.getGeometry();
-                    if (expGeom != null) {
-                        geomProperties.add(expGeom );
-                    } else {
-                        allDefined = false;
-                    }
-                }
-            }
-        } else {
-            allDefined = false;
-        }
-        if (!allDefined) {
-            // Add the default geometry property.
-            try {
-                PropertyType geomDesc = getDefaultGeometry(schema);
-                geomProperties.add(filterFactory.property(geomDesc.getName().toString()));
-            } catch (PropertyNotFoundException | IllegalStateException ex) {
-                // Do nothing.
-            }
-        }
-        if (geomProperties.isEmpty()) {
-            // No geometry selected for rendering.
-            query.setSelection(Filter.exclude());
-            return query;
-        }
-        final Envelope bbox = optimizeBBox(canvas, symbolsMargin);
-        Filter<? super Feature> filter;
-        // Make a bbox filter.
-        if (geomProperties.size() == 1) {
-            final Expression<Feature,?> geomExp = geomProperties.iterator().next();
-            filter = filterFactory.bbox(geomExp, bbox);
-        } else {
-            // Make an OR filter with all geometries.
-            final List<Filter<?>> geomFilters = new ArrayList<>();
-            for (final Expression<Feature,?> geomExp : geomProperties) {
-                geomFilters.add(filterFactory.bbox(geomExp, bbox));
-            }
-            filter = filterFactory.or((List) geomFilters);      // TODO
-        }
-        /*
-         * Combine the filter with rule filters.
-         */
-        ruleOpti:
-        if (rules != null) {
-            final List<Filter<Feature>> rulefilters = new ArrayList<>();
-            for (final Rule rule : rules) {
-                if (rule.isElseFilter()) {
-                    // We can not append styling filters, an else rule match all features.
-                    break ruleOpti;
-                } else {
-                    final Filter<Feature> rf = rule.getFilter();
-                    if (rf == null || rf == Filter.<Feature>include()) {
-                        // We can not append styling filters, this rule matchs all features.
-                        break ruleOpti;
-                    }
-                    rulefilters.add(rf);
-                }
-            }
-            final Filter<Feature> combined;
-            if (rulefilters.size() == 1) {
-//                //TODO need a stylefactory in SIS
-//                //special case, only one rule and we passed the filter to the query
-//                //we can remove it from the rule
-//                final Rule original = rules.get(0);
-//                Rule rule = styleFactory.rule(null, null, null,
-//                        original.getMinScaleDenominator(),
-//                        original.getMaxScaleDenominator(),
-//                        new ArrayList(original.symbolizers()),
-//                        Filter.include());
-//                rules.set(0, rule);
-                combined = rulefilters.get(0);
-            } else {
-                combined = filterFactory.or((List) rulefilters);        // TODO
-            }
-            if (filter != Filter.include()) {
-                filter = filterFactory.and((Filter) filter, combined);  // TODO
-            } else {
-                filter = combined;
-            }
-        }
-        query.setSelection(filter);
-        /*
-         * Reduce requiered attributes.
-         */
-        if (requiredProperties == null) {
-            // All properties are required.
-        } else {
-            final Set<String> copy = new HashSet<>();
-            // Add used properties.
-            for (final String str : requiredProperties) {
-                copy.add(stripXpath(str));
-            }
-            // Add properties used as geometry.
-            for (Expression<?,?> exp : geomProperties) {
-                final PropertyNameCollector collector = new PropertyNameCollector();
-                collector.visit(exp);
-                collector.references.stream()
-                        .map(SEPortrayer::stripXpath)
-                        .forEach(copy::add);
-            }
-            try {
-                // Always include the identifier if it exist.
-                schema.getProperty(AttributeConvention.IDENTIFIER);
-                copy.add(AttributeConvention.IDENTIFIER);
-            } catch (PropertyNotFoundException ex) {
-                // No id, ignore it.
-            }
-            final List<FeatureQuery.NamedExpression> columns = new ArrayList<>();
-            for (String propName : copy) {
-                columns.add(new FeatureQuery.NamedExpression(filterFactory.property(propName), propName));
-            }
-            query.setProjection(columns.toArray(new FeatureQuery.NamedExpression[columns.size()]));
-        }
-        //TODO optimize filter
-        //TODO add linear resolution
-        return query;
-    }
-
-    /**
-     * Geographic scale calculated using OGC Symbology Encoding specification.
-     * This is not the scale Objective to Display.
-     * This is not an accurate geographic scale.
-     * This is a fake average scale unproper for correct rendering.
-     * It is used only to filter SE rules.
-     */
-    private static double getSEScale(final GridGeometry canvas, final AffineTransform objToDisp) {
-        final Envelope envelope = canvas.getEnvelope();
-        final CoordinateReferenceSystem objCRS = envelope.getCoordinateReferenceSystem();
-        final long width = canvas.getExtent().getSize(0);
-        if (AffineTransforms2D.getRotation(objToDisp) != 0.0) {
-            final double scale = AffineTransforms2D.getScale(objToDisp);
-            if (objCRS instanceof GeographicCRS) {
-                return (SE_DEGREE_TO_METERS * DEFAULT_DPI) / (scale*PIXEL_SIZE);
-            } else {
-                return DEFAULT_DPI / (scale *PIXEL_SIZE);
-            }
-        } else {
-            if (objCRS instanceof GeographicCRS) {
-                return (envelope.getSpan(0) * SE_DEGREE_TO_METERS) / (width / DEFAULT_DPI * PIXEL_SIZE);
-            } else {
-                return envelope.getSpan(0) / (width / DEFAULT_DPI * PIXEL_SIZE);
-            }
-        }
-    }
-
-    /**
-     * List the valid rules for current scale and type.
-     */
-    private static List<Rule> getValidRules(final FeatureTypeStyle fts, final double scale, final FeatureType type) {
-        final Set<GenericName> names = fts.featureTypeNames();
-        if (!names.isEmpty()) {
-            // TODO: should we check parent types?
-            boolean found = false;
-            for (GenericName name : names) {
-                if (name.equals(type.getName())) {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found) {
-                return Collections.emptyList();
-            }
-        }
-        // Check semantic, only if we have a feature type.
-        if (type != null) {
-            final Collection<SemanticType> semantics = fts.semanticTypeIdentifiers();
-            if (!semantics.isEmpty()) {
-                Class<?> ctype;
-                try {
-                    ctype = Features.toAttribute(getDefaultGeometry(type))
-                            .map(AttributeType::getValueClass)
-                            .orElse(null);
-                } catch (PropertyNotFoundException e) {
-                      ctype = null;
-                }
-                boolean valid = false;
-                for (SemanticType semantic : semantics) {
-                    if (semantic == SemanticType.ANY) {
-                        valid = true;
-                        break;
-                    } else if (semantic == SemanticType.LINE) {
-                        if (ctype == LineString.class || ctype == MultiLineString.class || ctype == Geometry.class) {
-                            valid = true;
-                            break;
-                        }
-                    } else if (semantic == SemanticType.POINT) {
-                        if (ctype == Point.class || ctype == MultiPoint.class || ctype == Geometry.class) {
-                            valid = true;
-                            break;
-                        }
-                    } else if (semantic == SemanticType.POLYGON) {
-                        if (ctype == Polygon.class || ctype == MultiPolygon.class || ctype == Geometry.class) {
-                            valid = true;
-                            break;
-                        }
-                    } else if (semantic == SemanticType.RASTER) {
-                        // Can not test this on feature datas.
-                    } else if (semantic == SemanticType.TEXT) {
-                        // Can not define a `text` type with current API.
-                    }
-                }
-                if (!valid) return Collections.emptyList();
-            }
-        }
-
-        //TODO filter correctly possibilities
-        //test if the featutetype is valid
-        //we move to next feature  type if not valid
-        //if (typeName != null && !(typeName.equalsIgnoreCase(fts.getFeatureTypeName())) ) continue;
-
-        final List<? extends Rule> rules = fts.rules();
-        final List<Rule> validRules = new ArrayList<>();
-        for (final Rule rule : rules) {
-            //test if the scale is valid for this rule
-            if (rule.getMinScaleDenominator() - SE_EPSILON <= scale && rule.getMaxScaleDenominator() + SE_EPSILON > scale) {
-                validRules.add(rule);
-            }
-        }
-        return validRules;
-    }
-
-    /**
-     * Lists all properties used in given rules.
-     */
-    private static Set<String> propertiesNames(final Collection<? extends Rule> rules) {
-        final PropertyNameCollector collector = new PropertyNameCollector();
-        for (final Rule r : rules) {
-            collector.visit(r);
-            collector.visit(r.getFilter());
-        }
-        return collector.references;
-    }
-
-    /**
-     * Sorts the rules, isolate the else rules, they must be handle differently
-     *
-     * @return index of starting else rules.
-     */
-    private static int sortByElseRule(final List<Rule> sortedRules){
-        int elseRuleIndex = sortedRules.size();
-        for (int i = 0; i < elseRuleIndex; i++) {
-            final Rule r = sortedRules.get(i);
-            if (r.isElseFilter()) {
-                elseRuleIndex--;
-                // Move the rule at the end
-                sortedRules.remove(i);
-                sortedRules.add(r);
-            }
-        }
-        return elseRuleIndex;
-    }
-
-    /**
-     * Search for the main geometric property in the given type. We'll search
-     * for an SIS convention first (see
-     * {@link AttributeConvention#GEOMETRY_PROPERTY}. If no convention is set on
-     * the input type, we'll check if it contains a single geometric property.
-     * If it's the case, we return it. Otherwise (no or multiple geometries), we
-     * throw an exception.
-     *
-     * @param  type  the data type to search into.
-     * @return the main geometric property we've found.
-     * @throws PropertyNotFoundException if no geometric property is available in the given type.
-     * @throws IllegalStateException if no convention is set (see {@link AttributeConvention#GEOMETRY_PROPERTY}),
-     *         and we have found more than one geometry.
-     */
-    private static PropertyType getDefaultGeometry(final FeatureType type) throws PropertyNotFoundException, IllegalStateException {
-        PropertyType geometry;
-        try {
-            geometry = type.getProperty(AttributeConvention.GEOMETRY_PROPERTY.toString());
-        } catch (PropertyNotFoundException e) {
-            try {
-                geometry = searchForGeometry(type);
-            } catch (RuntimeException e2) {
-                e2.addSuppressed(e);
-                throw e2;
-            }
-        }
-        return geometry;
-    }
-
-    /**
-     * Searches for a geometric attribute outside SIS conventions. More accurately,
-     * we expect the given type to have a single geometry attribute. If many are
-     * found, an exception is thrown.
-     *
-     * @param  type  the data type to search into.
-     * @return the only geometric property we've found.
-     * @throws PropertyNotFoundException if no geometric property is available in the given type.
-     * @throws IllegalStateException if we have found more than one geometry.
-     */
-    private static PropertyType searchForGeometry(final FeatureType type) throws PropertyNotFoundException, IllegalStateException {
-        final List<? extends PropertyType> geometries = type.getProperties(true).stream()
-                .filter(IS_NOT_CONVENTION)
-                .filter(AttributeConvention::isGeometryAttribute)
-                .collect(Collectors.toList());
-
-        if (geometries.size() < 1) {
-            throw new PropertyNotFoundException("No geometric property can be found outside of sis convention.");
-        } else if (geometries.size() > 1) {
-            throw new IllegalStateException("Multiple geometries found. We don't know which one to select.");
-        } else {
-            return geometries.get(0);
-        }
-    }
-
-    /**
-     * Removes any xpath elements, keep only the root property name.
-     */
-    private static String stripXpath(String attName) {
-        int index = attName.indexOf('/');
-        if (index == 0) {
-            attName = attName.substring(1);             // Remove first slash
-            final Pattern pattern = Pattern.compile("(\\{[^\\{\\}]*\\})|(\\[[^\\[\\]]*\\])|/{1}");
-            final Matcher matcher = pattern.matcher(attName);
-            final StringBuilder sb = new StringBuilder();
-            int position = 0;
-matches:    while (matcher.find()) {
-                final String match = matcher.group();
-                sb.append(attName.substring(position, matcher.start()));
-                position = matcher.end();
-                switch (match.charAt(0)) {
-                    case '/': {
-                        // We do not query precisely sub elements.
-                        position = attName.length();
-                        break matches;
-                    }
-                    case '{': {
-                        sb.append(match);
-                        break;
-                    }
-                    case '[': {
-                        // Strip indexes or xpath searches.
-                        break;
-                    }
-                }
-            }
-            sb.append(attName.substring(position));
-            attName = sb.toString();
-        }
-        return attName;
-    }
-
-    /**
-     * Extracts envelope and expand it's horizontal component by given margin.
-     */
-    private static Envelope optimizeBBox(GridGeometry canvas, double symbolsMargin) throws TransformException {
-        Envelope env = canvas.getEnvelope();
-        //keep only horizontal component
-        env = Envelopes.transform(env, CRS.getHorizontalComponent(env.getCoordinateReferenceSystem()));
-
-        //expand the search area by given margin
-        if (symbolsMargin > 0) {
-            final GeneralEnvelope e = new GeneralEnvelope(env);
-            e.setRange(0, e.getMinimum(0) - symbolsMargin, e.getMaximum(0) + symbolsMargin);
-            e.setRange(1, e.getMinimum(1) - symbolsMargin, e.getMaximum(1) + symbolsMargin);
-            env = e;
-        }
-        return env;
-    }
-}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPresentation.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPresentation.java
deleted file mode 100644
index 3e0a60a..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPresentation.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.map;
-
-import java.util.Objects;
-import org.apache.sis.portrayal.MapLayer;
-import org.apache.sis.storage.Resource;
-import org.opengis.feature.Feature;
-import org.opengis.style.Symbolizer;
-
-/**
- * A presentation build with a standard Symbology Encoding Symbolizer.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-public final class SEPresentation extends Presentation {
-
-    private Symbolizer symbolizer;
-
-    public SEPresentation() {
-    }
-
-    public SEPresentation(MapLayer layer, Resource resource, Feature candidate, Symbolizer symbolizer) {
-        super(layer, resource, candidate);
-        this.symbolizer = symbolizer;
-    }
-
-    /**
-     * @return Symbogy Encoding symbolizer
-     */
-    public Symbolizer getSymbolizer() {
-        return symbolizer;
-    }
-
-    public void setSymbolizer(Symbolizer symbolizer) {
-        this.symbolizer = symbolizer;
-    }
-
-    @Override
-    public int hashCode() {
-        int hash = 7;
-        hash = 71 * hash + Objects.hashCode(this.symbolizer);
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final SEPresentation other = (SEPresentation) obj;
-        if (!Objects.equals(this.symbolizer, other.symbolizer)) {
-            return false;
-        }
-        return super.equals(obj);
-    }
-
-}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SymbologyVisitor.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SymbologyVisitor.java
deleted file mode 100644
index 091805a..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SymbologyVisitor.java
+++ /dev/null
@@ -1,394 +0,0 @@
-/*
- * 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.map;
-
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-import org.opengis.style.AnchorPoint;
-import org.opengis.style.ChannelSelection;
-import org.opengis.style.ColorMap;
-import org.opengis.style.ColorReplacement;
-import org.opengis.style.ContrastEnhancement;
-import org.opengis.style.Description;
-import org.opengis.style.Displacement;
-import org.opengis.style.ExtensionSymbolizer;
-import org.opengis.style.ExternalGraphic;
-import org.opengis.style.ExternalMark;
-import org.opengis.style.FeatureTypeStyle;
-import org.opengis.style.Fill;
-import org.opengis.style.Font;
-import org.opengis.style.Graphic;
-import org.opengis.style.GraphicFill;
-import org.opengis.style.GraphicLegend;
-import org.opengis.style.GraphicStroke;
-import org.opengis.style.GraphicalSymbol;
-import org.opengis.style.Halo;
-import org.opengis.style.LabelPlacement;
-import org.opengis.style.LinePlacement;
-import org.opengis.style.LineSymbolizer;
-import org.opengis.style.Mark;
-import org.opengis.style.PointPlacement;
-import org.opengis.style.PointSymbolizer;
-import org.opengis.style.PolygonSymbolizer;
-import org.opengis.style.RasterSymbolizer;
-import org.opengis.style.Rule;
-import org.opengis.style.SelectedChannelType;
-import org.opengis.style.ShadedRelief;
-import org.opengis.style.Stroke;
-import org.opengis.style.Style;
-import org.opengis.style.Symbolizer;
-import org.opengis.style.TextSymbolizer;
-
-import static org.apache.sis.internal.util.CollectionsExt.nonNull;
-
-
-/**
- * Loops on all objects contained in a style.
- * Sub classes are expected to override interested methods to fill their objectives.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-public abstract class SymbologyVisitor {
-    protected SymbologyVisitor() {
-    }
-
-    protected void visit(final Style candidate) {
-        if (candidate != null) {
-            nonNull(candidate.featureTypeStyles()).forEach(this::visit);
-        }
-    }
-
-    protected void visit(final FeatureTypeStyle candidate) {
-        if (candidate != null) {
-            nonNull(candidate.rules()).forEach(this::visit);
-        }
-    }
-
-    protected void visit(final Rule candidate) {
-        if (candidate != null) {
-            nonNull(candidate.symbolizers()).forEach(this::visit);
-        }
-    }
-
-    protected void visit(final Symbolizer candidate) {
-        if (candidate instanceof PointSymbolizer) {
-            visit((PointSymbolizer) candidate);
-        } else if (candidate instanceof LineSymbolizer) {
-            visit((LineSymbolizer) candidate);
-        } else if (candidate instanceof PolygonSymbolizer) {
-            visit((PolygonSymbolizer) candidate);
-        } else if (candidate instanceof TextSymbolizer) {
-            visit((TextSymbolizer) candidate);
-        } else if (candidate instanceof RasterSymbolizer) {
-            visit((RasterSymbolizer) candidate);
-        } else if (candidate instanceof ExtensionSymbolizer) {
-            visit((ExtensionSymbolizer) candidate);
-        } else if (candidate != null) {
-            throw new IllegalArgumentException("Unexpected symbolizer " + candidate);
-        }
-    }
-
-    protected void visit(final PointSymbolizer candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            visit(candidate.getGraphic());
-        }
-    }
-
-    protected void visit(final LineSymbolizer candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            visit(candidate.getPerpendicularOffset());
-            visit(candidate.getStroke());
-        }
-    }
-
-    protected void visit(final PolygonSymbolizer candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            visit(candidate.getPerpendicularOffset());
-            visit(candidate.getDisplacement());
-            visit(candidate.getFill());
-            visit(candidate.getStroke());
-        }
-    }
-
-    protected void visit(final TextSymbolizer candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            visit(candidate.getLabel());
-            visit(candidate.getFill());
-            visit(candidate.getFont());
-            visit(candidate.getHalo());
-            visit(candidate.getLabelPlacement());
-        }
-    }
-
-    protected void visit(final RasterSymbolizer candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            visit(candidate.getOpacity());
-            visit(candidate.getChannelSelection());
-            visit(candidate.getColorMap());
-            visit(candidate.getContrastEnhancement());
-            visit(candidate.getImageOutline());
-            visit(candidate.getShadedRelief());
-        }
-    }
-
-    protected void visit(final ExtensionSymbolizer candidate) {
-        if (candidate != null) {
-            visit(candidate.getGeometry());
-            candidate.getParameters().values().forEach(this::visit);
-        }
-    }
-
-    protected void visit(final Graphic candidate) {
-        if (candidate != null) {
-            visit(candidate.getOpacity());
-            visit(candidate.getRotation());
-            visit(candidate.getSize());
-            visit(candidate.getAnchorPoint());
-            visit(candidate.getDisplacement());
-            nonNull(candidate.graphicalSymbols()).forEach(this::visit);
-        }
-    }
-
-    protected void visit(final GraphicalSymbol candidate) {
-        if (candidate instanceof Mark) {
-            visit((Mark) candidate);
-        } else if (candidate instanceof ExternalGraphic) {
-            visit((ExternalGraphic) candidate);
-        } else if (candidate != null) {
-            throw new IllegalArgumentException("Unexpected GraphicalSymbol " + candidate);
-        }
-    }
-
-    protected void visit(final Mark candidate) {
-        if (candidate != null) {
-            visit(candidate.getExternalMark());
-            visit(candidate.getFill());
-            visit(candidate.getStroke());
-            visit(candidate.getWellKnownName());
-        }
-    }
-
-    protected void visit(final ExternalGraphic candidate) {
-        nonNull(candidate.getColorReplacements()).forEach(this::visit);
-    }
-
-    protected void visit(final ExternalMark candidate) {
-    }
-
-    protected void visit(final Stroke candidate) {
-        if (candidate != null) {
-            visit(candidate.getColor());
-            visit(candidate.getDashOffset());
-            visit(candidate.getGraphicFill());
-            visit(candidate.getGraphicStroke());
-            visit(candidate.getLineCap());
-            visit(candidate.getLineJoin());
-            visit(candidate.getOpacity());
-            visit(candidate.getWidth());
-        }
-    }
-
-    protected void visit(final Description candidate) {
-    }
-
-    protected void visit(final Displacement candidate) {
-        if (candidate != null) {
-            visit(candidate.getDisplacementX());
-            visit(candidate.getDisplacementY());
-        }
-    }
-
-    protected void visit(final Fill candidate) {
-        if (candidate != null) {
-            visit(candidate.getGraphicFill());
-            visit(candidate.getColor());
-            visit(candidate.getOpacity());
-        }
-    }
-
-    protected void visit(final Font candidate) {
-        if (candidate != null) {
-            candidate.getFamily().forEach(this::visit);
-            visit(candidate.getSize());
-            visit(candidate.getStyle());
-            visit(candidate.getWeight());
-        }
-    }
-
-    protected void visit(final GraphicFill candidate) {
-        visit((Graphic) candidate);
-    }
-
-    protected void visit(final GraphicStroke candidate) {
-        if (candidate != null) {
-            visit((Graphic) candidate);
-            visit(candidate.getGap());
-            visit(candidate.getInitialGap());
-        }
-    }
-
-    protected void visit(final LabelPlacement candidate) {
-        if (candidate instanceof PointPlacement) {
-            visit((PointPlacement) candidate);
-        } else if (candidate instanceof LinePlacement) {
-            visit((LinePlacement) candidate);
-        } else if (candidate != null) {
-            throw new IllegalArgumentException("Unexpected Placement " + candidate);
-        }
-    }
-
-    protected void visit(final PointPlacement candidate) {
-        if (candidate != null) {
-            visit(candidate.getAnchorPoint());
-            visit(candidate.getDisplacement());
-            visit(candidate.getRotation());
-        }
-    }
-
-    protected void visit(final AnchorPoint candidate) {
-        if (candidate != null) {
-            visit(candidate.getAnchorPointX());
-            visit(candidate.getAnchorPointY());
-        }
-    }
-
-    protected void visit(final LinePlacement candidate) {
-        if (candidate != null) {
-            visit(candidate.getGap());
-            visit(candidate.getInitialGap());
-            visit(candidate.getPerpendicularOffset());
-        }
-    }
-
-    protected void visit(final GraphicLegend candidate) {
-        visit((Graphic) candidate);
-    }
-
-    protected void visit(final Halo candidate) {
-        if (candidate != null) {
-            visit(candidate.getFill());
-            visit(candidate.getRadius());
-        }
-    }
-
-    protected void visit(final ColorMap candidate) {
-        if (candidate != null) {
-            visit(candidate.getFunction());
-        }
-    }
-
-    protected void visit(final ColorReplacement candidate) {
-        if (candidate != null) {
-            visit(candidate.getRecoding());
-        }
-    }
-
-    protected void visit(final ContrastEnhancement candidate) {
-        if (candidate != null) {
-            visit(candidate.getGammaValue());
-        }
-    }
-
-    protected void visit(final ChannelSelection candidate) {
-        if (candidate != null) {
-            visit(candidate.getGrayChannel());
-            final SelectedChannelType[] rgbChannels = candidate.getRGBChannels();
-            if (rgbChannels != null) {
-                for (final SelectedChannelType sct : rgbChannels) {
-                    visit(sct);
-                }
-            }
-        }
-    }
-
-    protected void visit(final SelectedChannelType candidate) {
-        if (candidate != null) {
-            visit(candidate.getContrastEnhancement());
-        }
-    }
-
-    protected void visit(final ShadedRelief candidate) {
-        if (candidate != null) {
-            visit(candidate.getReliefFactor());
-        }
-    }
-
-    /**
-     * Find all value references and literal in the given expression and its parameters.
-     * This method invokes itself recursively.
-     *
-     * @param  candidate  the filter to examine, or {@code null} if none.
-     */
-    protected void visit(final Filter<?> candidate) {
-        if (candidate != null) {
-            if (candidate instanceof LogicalOperator<?>) {
-                ((LogicalOperator<?>) candidate).getOperands().forEach(this::visit);
-            } else {
-                candidate.getExpressions().forEach(this::visit);
-            }
-        }
-    }
-
-    /**
-     * Find all value references and literal in the given expression and its parameters.
-     * This method invokes itself recursively.
-     *
-     * @param  candidate  the expression to examine, or {@code null} if none.
-     */
-    protected void visit(final Expression<?,?> candidate) {
-        if (candidate != null) {
-            if (candidate instanceof ValueReference<?,?>) {
-                visitProperty((ValueReference<?,?>) candidate);
-            } else if (candidate instanceof Literal<?,?>) {
-                visitLiteral((Literal<?,?>) candidate);
-            } else {
-                candidate.getParameters().forEach(this::visit);
-            }
-        }
-    }
-
-    /**
-     * Invoked by {@link #visit(Expression)} for each value reference.
-     *
-     * @param  expression  a value reference found in a chain of expressions.
-     */
-    protected void visitProperty(ValueReference<?,?> expression) {
-    }
-
-    /**
-     * Invoked by {@link #visit(Expression)} for each literal.
-     *
-     * @param  expression  a literal found in a chain of expressions.
-     */
-    protected void visitLiteral(Literal<?,?> expression) {
-    }
-}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/package-info.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/package-info.java
deleted file mode 100644
index 7a806ba..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/package-info.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.
- */
-
-
-/**
- * Symbology and map representations, together with a rendering engine for display.
- *
- * <p><b>WARNING:</b> this package is work in progress and is not yet part of public API.
- * Some classes in this package will move to public API after we gained enough confidence
- * about their stability.</p>
- *
- * <h2>Synchronization</h2>
- * Unless otherwise specified, classes in this package are not thread-safe.
- * Synchronization, if desired, must be done by the caller.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-package org.apache.sis.internal.map;
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/Canvas.java b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/Canvas.java
index 7e7c1f3..4e197db 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/Canvas.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/Canvas.java
@@ -24,7 +24,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -34,7 +33,6 @@
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.datum.PixelInCell;
-import org.opengis.coverage.CannotEvaluateException;
 import org.opengis.util.FactoryException;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Localized;
@@ -60,6 +58,10 @@
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
 
+// Branch-dependent imports
+import org.apache.sis.geometry.MismatchedReferenceSystemException;
+import org.apache.sis.coverage.CannotEvaluateException;
+
 
 /**
  * Common abstraction for implementations that manage the display and user manipulation
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/MapItem.java b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/MapItem.java
deleted file mode 100644
index 573619e..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/MapItem.java
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * 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.portrayal;
-
-import java.beans.PropertyChangeListener;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import org.apache.sis.storage.DataStoreException;
-import org.opengis.geometry.Envelope;
-import org.opengis.util.InternationalString;
-
-
-/**
- * Base class of map layer or group of map layers. This base class does not represent graphical elements.
- * Instead it contains information (data and style) for creating a tree of portrayal objects.
- * A {@code MapItem} contains the following properties:
- *
- * <ul>
- *   <li>An {@linkplain #getIdentifier() identifier}, which can be any {@link String} at developer choice.</li>
- *   <li>A human-readable {@linkplain #getTitle() title} for pick lists, for example in GUI.</li>
- *   <li>A {@linkplain #getAbstract() narrative description} providing more details.</li>
- * </ul>
- *
- * Additional information can be added in a map of {@linkplain #getUserProperties() user properties}.
- * The actual feature or coverage data, together with styling information, are provided by subclasses.
- *
- * <h2>Synchronization</h2>
- * {@code MapItem} instances are not thread-safe. Synchronization, if desired, is caller responsibility.
- *
- * @todo Rename as {@code LayerNode}? "Item" suggests an element in a list, while {@link MapLayers} actually
- *       creates a tree. Furthermore having {@code Layer} in the name would add emphasis that this is a tree
- *       of layers and not a tree of arbitrary objects.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-public abstract class MapItem extends Observable {
-    /**
-     * The {@value} property name, used for notifications about changes in map item identifier.
-     * The identifier (or name) can be used to reference the item externally.
-     * Associated values are instances of {@link String}.
-     *
-     * @see #getIdentifier()
-     * @see #setIdentifier(String)
-     * @see #addPropertyChangeListener(String, PropertyChangeListener)
-     *
-     * @todo This property seems to be named {@code "se:Name"} in SLD specification. Should we rename?
-     */
-    public static final String IDENTIFIER_PROPERTY = "identifier";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map item title.
-     * The title is a short description for item that might be displayed in a GUI pick list.
-     * Associated values are instances of {@link String} or {@link InternationalString}.
-     *
-     * @see #getTitle()
-     * @see #setTitle(CharSequence)
-     * @see #addPropertyChangeListener(String, PropertyChangeListener)
-     */
-    public static final String TITLE_PROPERTY = "title";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map item description.
-     * The abstract is a narrative description providing additional information.
-     * It is more detailed than the {@value #TITLE_PROPERTY} property and may be a few paragraphs long.
-     * Associated values are instances of {@link String} or {@link InternationalString}.
-     *
-     * @see #getAbstract()
-     * @see #setAbstract(CharSequence)
-     * @see #addPropertyChangeListener(String, PropertyChangeListener)
-     */
-    public static final String ABSTRACT_PROPERTY = "abstract";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map item visibility state.
-     * Associated values are instances of {@link Boolean}.
-     */
-    public static final String VISIBLE_PROPERTY = "visible";
-
-    /**
-     * Identifier of this map item.
-     *
-     * @see #IDENTIFIER_PROPERTY
-     * @see #getIdentifier()
-     */
-    private String identifier;
-
-    /**
-     * The title of this map item, for display to the user.
-     *
-     * @see #TITLE_PROPERTY
-     * @see #getTitle()
-     */
-    private CharSequence title;
-
-    /**
-     * A description of this map item, for display to the user. The property name is
-     * {@value #ABSTRACT_PROPERTY} but we use a different field name because "abstract"
-     * is a reserved keyword.
-     *
-     * @see #ABSTRACT_PROPERTY
-     * @see #getAbstract()
-     */
-    private CharSequence description;
-
-    /**
-     * Whether this item should be shown on the map.
-     *
-     * @see #VISIBLE_PROPERTY
-     * @see #isVisible()
-     */
-    private boolean visible;
-
-    /**
-     * Additional user defined properties, created when first requested.
-     *
-     * @see #getUserProperties()
-     */
-    private Map<String,Object> userMap;
-
-    /**
-     * Only used by classes in this package.
-     */
-    MapItem() {
-        visible = true;
-    }
-
-    /**
-     * Returns the identifier of this map item. The identifier can be any character string at developer choice;
-     * there is currently no restriction on identifier syntax and no restriction about identifier uniqueness.
-     * That identifier is currently not used by Apache SIS; it is made available as a user convenience for
-     * referencing {@code MapItem} instances externally.
-     *
-     * <p>NOTE: restriction about identifier syntax and uniqueness may be added in a future version.</p>
-     *
-     * @return identifier, or {@code null} if none.
-     *
-     * @see #IDENTIFIER_PROPERTY
-     */
-    public String getIdentifier() {
-        return identifier;
-    }
-
-    /**
-     * Sets a new identifier for this map item. If this method is never invoked, the default value is {@code null}.
-     * If the given value is different than the previous value, then a change event is sent to all listeners
-     * registered for the {@value #IDENTIFIER_PROPERTY} property.
-     *
-     * @param  newValue  the new identifier, or {@code null} if none.
-     */
-    public void setIdentifier(final String newValue) {
-        final String oldValue = identifier;
-        if (!Objects.equals(oldValue, newValue)) {
-            identifier = newValue;
-            firePropertyChange(IDENTIFIER_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns a human-readable short description for pick lists.
-     * This title should be user friendly and may be a {@link String} or {@link InternationalString} instance.
-     * It shall not be used as an identifier.
-     *
-     * @return a short description to be shown to the user, or {@code null} if none.
-     *
-     * @see #TITLE_PROPERTY
-     */
-    public CharSequence getTitle() {
-        return title;
-    }
-
-    /**
-     * Sets a new human-readable short description for pick lists. If this method is never invoked,
-     * the default value is {@code null}. If the given value is different than the previous value,
-     * then a change event is sent to all listeners registered for the {@value #TITLE_PROPERTY} property.
-     *
-     * @param  newValue  a short description to be shown to the user, or {@code null} if none.
-     */
-    public void setTitle(final CharSequence newValue) {
-        final CharSequence oldValue = title;
-        if (!Objects.equals(oldValue, newValue)) {
-            title = newValue;
-            firePropertyChange(TITLE_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns a narrative description providing additional information.
-     * The abstract is more detailed than the {@linkplain #getTitle() title} property and may be a few paragraphs long.
-     * This abstract should be user friendly and may be a {@link String} or {@link InternationalString} instance.
-     *
-     * @return narrative description to be shown to the user, or {@code null} if none.
-     *
-     * @see #ABSTRACT_PROPERTY
-     */
-    public CharSequence getAbstract() {
-        return description;
-    }
-
-    /**
-     * Sets a new a narrative description providing additional information. If this method is never invoked,
-     * the default value is {@code null}. If the given value is different than the previous value, then
-     * a change event is sent to all listeners registered for the {@value #ABSTRACT_PROPERTY} property.
-     *
-     * @param  newValue  a narrative description to be shown to the user, or {@code null} if none.
-     */
-    public void setAbstract(final CharSequence newValue) {
-        final CharSequence oldValue = description;
-        if (!Objects.equals(oldValue, newValue)) {
-            description = newValue;
-            firePropertyChange(ABSTRACT_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns whether this item should be shown on the map. If this item is a {@code MapGroup},
-     * then a {@code false} visibility status implies that all group components are also hidden.
-     *
-     * @return {@code true} if this item is visible.
-     *
-     * @see #VISIBLE_PROPERTY
-     */
-    public boolean isVisible() {
-        return visible;
-    }
-
-    /**
-     * Sets whether this item should be shown on the map.
-     * If this method is never invoked, the default value is {@code true}.
-     * If the given value is different than the previous value, then a change event
-     * is sent to all listeners registered for the {@value #VISIBLE_PROPERTY} property.
-     *
-     * <p>If this item is a {@code MapLayers}, then hiding this group should hide all components in this group,
-     * but without changing the individual {@value #VISIBLE_PROPERTY} property of those components.
-     * Consequently making the group visible again restore each component to the visibility state
-     * it has before the group was hidden (assuming those states have not been changed in other ways).</p>
-     *
-     * @param  newValue  {@code false} to hide this item and all it's components.
-     */
-    public void setVisible(final boolean newValue) {
-        final boolean oldValue = visible;
-        if (oldValue != newValue) {
-            visible = newValue;
-            firePropertyChange(VISIBLE_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns the envelope of this {@code MapItem}.
-     * If this instance is a {@code MapLayers} the envelope is the concatenation of all it's components,
-     * in case of multiple CRS for each MapLayer, the resulting envelope CRS is unpredictable.
-     * If this instance is a {@code MapLayer} the envelope is the resource data envelope.
-     *
-     * @return the spatiotemporal extent. May be absent if none or too costly to compute.
-     * @throws DataStoreException if an error occurred while reading or computing the envelope.
-     */
-    public Optional<Envelope> getEnvelope() throws DataStoreException {
-        return Optional.empty();
-    }
-
-    /**
-     * Returns a modifiable map of user properties.
-     * The content of this map is left to users; Apache SIS does not use it in any way.
-     * This map is not thread-safe; synchronization if desired is user responsibility.
-     *
-     * @return map of user properties. This map is live: changes in this map
-     *         are immediately reflected in this {@code MapItem}.
-     */
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Map<String,Object> getUserProperties() {
-        if (userMap == null) {
-            userMap = new HashMap<>();
-        }
-        return userMap;
-    }
-}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/MapLayer.java b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/MapLayer.java
deleted file mode 100644
index b2b97e4..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/MapLayer.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * 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.portrayal;
-
-import java.util.Objects;
-import java.util.Optional;
-import org.apache.sis.storage.Aggregate;
-import org.apache.sis.storage.DataSet;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.Query;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.util.ArgumentChecks;
-import org.opengis.coverage.Coverage;
-import org.opengis.feature.Feature;
-import org.opengis.geometry.Envelope;
-import org.opengis.style.Style;
-
-
-/**
- * Data (resource) associated to rules for visual representation (symbology).
- * Layers are the key elements of a map: they link data (given by {@link Resource}s) or a subset of
- * those data (filtered by {@link Query}) to their visual representation (defined by {@link Style}s).
- * The visual appearance of a layer should be similar with any rendering engine.
- * Some details may very because of different rendering strategies for label placements, 2D or 3D,
- * but the fundamentals aspect of each {@link Feature} or {@link Coverage} should be unchanged.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-public class MapLayer extends MapItem {
-    /**
-     * The {@value} property name, used for notifications about changes in map layer resource.
-     * The data resource provides the digital data to be rendered. Note that not all kinds of resources
-     * are digital data. For example a resource may be a citation of facts or figures printed on paper,
-     * photographic material, or other media (see all {@link org.opengis.metadata.citation.PresentationForm}
-     * values having the {@code _HARDCOPY} suffix in their name).
-     * Associated values should be instances of {@link DataSet} or {@link Aggregate}.
-     *
-     * @see #getData()
-     * @see #setData(Resource)
-     */
-    public static final String DATA_PROPERTY = "data";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map layer query.
-     * The query can filter resource data for rendering only a subset of available data.
-     * Associated values are instances of {@link Query}.
-     *
-     * @see #getQuery()
-     * @see #setQuery(Query)
-     */
-    public static final String QUERY_PROPERTY = "query";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map layer style.
-     * The style specifies the appearance of the filtered data to be rendered.
-     * Associated values are instances of {@link Style}.
-     *
-     * @see #getStyle()
-     * @see #setStyle(Style)
-     */
-    public static final String STYLE_PROPERTY = "style";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map layer opacity.
-     * The opacity specifies the gloabal opacity of the data to be rendered.
-     *
-     * @see #getOpacity()
-     * @see #setOpacity(double)
-     */
-    public static final String OPACITY_PROPERTY = "opacity";
-
-    /**
-     * Data to be rendered, or {@code null} if unavailable.
-     *
-     * @see #DATA_PROPERTY
-     * @see #getData()
-     */
-    private Resource resource;
-
-    /**
-     * Filter for rendering a subset of available data, or {@code null} if none.
-     *
-     * @see #QUERY_PROPERTY
-     * @see #getQuery()
-     */
-    private Query query;
-
-    /**
-     * Visual representation of data, or {@code null} if none.
-     *
-     * @see #STYLE_PROPERTY
-     * @see #getStyle()
-     */
-    private Style style;
-
-    /**
-     * Visual transparency of data, or {@code null} if none.
-     *
-     * @see #OPACITY_PROPERTY
-     * @see #getOpacity()
-     */
-    private double opacity = 1.0;
-
-    /**
-     * Constructs an initially empty map layer.
-     *
-     * @todo Expect {@code Resource} and {@code Style} in argument, for discouraging
-     *       the use of {@code MapLayer} with null resource and null style?
-     */
-    public MapLayer() {
-    }
-
-    /**
-     * Returns the data (resource) represented by this layer.
-     * The resource should be a {@link DataSet}, but {@link Aggregate} is also accepted.
-     * The behavior in aggregate case depends on the rendering engine.
-     *
-     * @return data to be rendered, or {@code null} is unavailable.
-     *
-     * @see #DATA_PROPERTY
-     */
-    public Resource getData() {
-        return resource;
-    }
-
-    /**
-     * Sets the data (resource) to be rendered.
-     * The resource should never be null, still the null case is tolerated to indicate
-     * that the layer should have existed but is unavailable for an unspecified reason.
-     * This case may happen with processing or distant services resources.
-     *
-     * <p>The given resource should be a {@link DataSet} or an {@link Aggregate} of data sets.
-     * However this base class does not enforce those types. Subclasses may restrict the set
-     * of resource types accepted by this method.</p>
-     *
-     * <p>Note that not all kinds of resources are digital data. For example a resource may be an organization,
-     * or citation of facts, tables and figures printed on paper, photographic material, or other media
-     * (see all {@link org.opengis.metadata.citation.PresentationForm} values having the {@code _HARDCOPY}
-     * suffix in their name). The kind of resources in {@code MapLayer} shall be one of those representing
-     * digital data.</p>
-     *
-     * @param  newValue  the new data, or {@code null} if unavailable.
-     */
-    public void setData(final Resource newValue) {
-        final Resource oldValue = resource;
-        if (!Objects.equals(oldValue, newValue)) {
-            resource = newValue;
-            firePropertyChange(DATA_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns the filter for reducing the amount of data to render. Query filters can be
-     * specified for rendering a smaller amount of data than what the resource can provide.
-     * If the query is undefined, then all data will be rendered.
-     *
-     * @return filter for reducing data, or {@code null} for rendering all data.
-     *
-     * @see #QUERY_PROPERTY
-     */
-    public Query getQuery() {
-        return query;
-    }
-
-    /**
-     * Sets a filter for reducing the amount of data to render. If this method is never invoked, the default value
-     * is {@code null}. If the given value is different than the previous value, then a change event is sent to all
-     * listeners registered for the {@value #QUERY_PROPERTY} property.
-     *
-     * @param  newValue  filter for reducing data, or {@code null} for rendering all data.
-     */
-    public void setQuery(final Query newValue) {
-        final Query oldValue = query;
-        if (!Objects.equals(oldValue, newValue)) {
-            query = newValue;
-            firePropertyChange(QUERY_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns the visual appearance of the data.
-     * If the style is undefined, the behavior is left to the rendering engine.
-     * It is expected that a default style should be used.
-     *
-     * @return description of data visual appearance, or {@code null} if unspecified.
-     */
-    public Style getStyle() {
-        return style;
-    }
-
-    /**
-     * Sets the visual appearance of the data. If this method is never invoked, the default value is {@code null}.
-     * If the given value is different than the previous value, then a change event is sent to all listeners
-     * registered for the {@value #STYLE_PROPERTY} property.
-     *
-     * @param  newValue  description of data visual appearance, or {@code null} if unspecified.
-     */
-    public void setStyle(final Style newValue) {
-        final Style oldValue = style;
-        if (!Objects.equals(oldValue, newValue)) {
-            style = newValue;
-            firePropertyChange(STYLE_PROPERTY, oldValue, newValue);
-        }
-    }
-
-    /**
-     * Returns the global opacity of this layer.
-     * Based on the rendering context this property may be impossible to implement,
-     * it is therefor recommended to modify the style symbolizer opacity properties.
-     *
-     * @return opacity between 0.0 and 1.0
-     */
-    public double getOpacity() {
-        return opacity;
-    }
-
-    /**
-     * Sets the global rendering opacity of this layer.
-     *
-     * @param opacity must be betwen 0.0 and 1.0
-     */
-    public void setOpacity(double opacity) {
-        ArgumentChecks.ensureBetween(OPACITY_PROPERTY, 0.0, 1.0, opacity);
-        if (this.opacity != opacity) {
-            double old = this.opacity;
-            this.opacity = opacity;
-            firePropertyChange(OPACITY_PROPERTY, old, opacity);
-        }
-    }
-
-    /**
-     * Returns the envelope of this {@code MapLayer}.
-     * The envelope is the resource data envelope.
-     *
-     * @return the spatiotemporal extent. May be absent if none or too costly to compute.
-     * @throws DataStoreException if an error occurred while reading or computing the envelope.
-     */
-    @Override
-    public Optional<Envelope> getEnvelope() throws DataStoreException {
-        Resource data = getData();
-        if (data instanceof DataSet) {
-            return ((DataSet) data).getEnvelope();
-        }
-        return Optional.empty();
-    }
-}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/MapLayers.java b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/MapLayers.java
deleted file mode 100644
index 0ead548..0000000
--- a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/MapLayers.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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.portrayal;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import org.apache.sis.geometry.Envelopes;
-import org.apache.sis.geometry.ImmutableEnvelope;
-import org.apache.sis.internal.map.ListChangeEvent;
-import org.apache.sis.internal.map.NotifiedList;
-import org.apache.sis.measure.NumberRange;
-import org.apache.sis.storage.DataSet;
-import org.apache.sis.storage.DataStoreException;
-import org.opengis.geometry.Envelope;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.TransformException;
-
-
-/**
- * A group of layers to display together.
- * {@code MapLayers} can be used for grouping related layers under a same node.
- * This allows global actions, like {@linkplain #setVisible(boolean) hiding} background layers in one call.
- * A {@code MapLayers} can also contain nested {@code MapLayers}, thus forming a tree.
- * Since {@link MapLayer} and {@code MapLayers} are the only {@link MapItem} subclasses,
- * all leaves in this tree can only be {@link MapLayer} instances (assuming no {@code MapLayers} is empty).
- *
- * <p>A {@code MapLayers} is the root node of the tree of all layers to draw on the map,
- * unless there is only one layer to draw.
- * The {@link MapItem} children are listed by {@link #getComponents()} in <var>z</var> order.
- * In addition, {@code MapLayers} may define an {@linkplain #getAreaOfInterest() area of interest}
- * which should be zoomed by default when the map is rendered.</p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-public class MapLayers extends MapItem {
-    /**
-     * The {@value} property name, used for notifications about changes in area of interest.
-     * Associated values are instances of {@link Envelope}.
-     *
-     * @see #getAreaOfInterest()
-     * @see #setAreaOfInterest(Envelope)
-     */
-    public static final String AREA_OF_INTEREST_PROPERTY = "areaOfInterest";
-
-    /**
-     * The {@value} property name, used for notifications about changes in map item components.
-     *
-     * @see #getComponents()
-     * @see #addPropertyChangeListener(String, PropertyChangeListener)
-     */
-    public static final String COMPONENTS_PROPERTY = "components";
-
-    /**
-     * The components in this group, or an empty list if none.
-     *
-     * @todo Should be an observable list with event sent when an element is added/removed/modified.
-     */
-    private final List<MapItem> components = new NotifiedList<MapItem>() {
-        @Override
-        protected void notifyAdd(MapItem item, int index) {
-            firePropertyChange(ListChangeEvent.added(MapLayers.this, COMPONENTS_PROPERTY, components, item, index));
-        }
-
-        @Override
-        protected void notifyAdd(List<MapItem> items, NumberRange<Integer> range) {
-            firePropertyChange(ListChangeEvent.added(MapLayers.this, COMPONENTS_PROPERTY, components, items, range));
-        }
-
-        @Override
-        protected void notifyRemove(MapItem item, int index) {
-            firePropertyChange(ListChangeEvent.removed(MapLayers.this, COMPONENTS_PROPERTY, components, item, index));
-        }
-
-        @Override
-        protected void notifyRemove(List<MapItem> items, NumberRange<Integer> range) {
-            firePropertyChange(ListChangeEvent.removed(MapLayers.this, COMPONENTS_PROPERTY, components, items, range));
-        }
-
-        @Override
-        protected void notifyReplace(MapItem olditem, MapItem newitem, int index) {
-            firePropertyChange(ListChangeEvent.changed(MapLayers.this, COMPONENTS_PROPERTY, components));
-        }
-    };
-
-    /**
-     * The area of interest, or {@code null} is unspecified.
-     */
-    private ImmutableEnvelope areaOfInterest;
-
-    /**
-     * Creates an initially empty group of layers.
-     */
-    public MapLayers() {
-    }
-
-    /**
-     * Gets the modifiable list of children contained in this group.
-     * The elements in the list are sorted in rendering order.
-     * This means that the first rendered element, which will be below
-     * all other elements on the rendered map, is located at index zero.
-     *
-     * <p>The returned list is modifiable: changes in the returned list will
-     * be immediately reflected in this {@code MapLayers}, and conversely.</p>
-     *
-     * @return modifiable list of children in this group of layers.
-     */
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public List<MapItem> getComponents() {
-        return components;
-    }
-
-    /**
-     * Returns the map area to show by default.
-     * This is not necessarily the {@linkplain DataSet#getEnvelope() envelope of data}
-     * since one may want to zoom in a different spatiotemporal area.
-     *
-     * <p>The {@linkplain org.apache.sis.geometry.GeneralEnvelope#getCoordinateReferenceSystem() envelope CRS}
-     * provides the reference system to use by default for rendering the map. It may be different than the CRS
-     * of data. The returned envelope may have {@linkplain org.apache.sis.geometry.GeneralEnvelope#isAllNaN()
-     * all its coordinates set to NaN} if only the {@link CoordinateReferenceSystem} is specified.</p>
-     *
-     * @return map area to show by default, or {@code null} is unspecified.
-     *
-     * @see DataSet#getEnvelope()
-     */
-    public Envelope getAreaOfInterest() {
-        return areaOfInterest;
-    }
-
-    /**
-     * Sets the map area to show by default.
-     * The given envelope is not necessarily related to the data contained in this group.
-     * It may be wider, or smaller, and in a different {@link CoordinateReferenceSystem}.
-     *
-     * @param  newValue  new map area to show by default, or {@code null} is unspecified.
-     */
-    public void setAreaOfInterest(final Envelope newValue) {
-        final ImmutableEnvelope imenv = ImmutableEnvelope.castOrCopy(newValue);
-        final Envelope oldValue = areaOfInterest;
-        if (!Objects.equals(oldValue, imenv)) {
-            areaOfInterest = imenv;
-            firePropertyChange(AREA_OF_INTEREST_PROPERTY, oldValue, imenv);
-        }
-    }
-
-    /**
-     * Returns the envelope of this {@code MapItem}.
-     * If this instance is a {@code MapLayers} the envelope is the concatenation of all it's components,
-     * in case of multiple CRS for each MapLayer, the resulting envelope CRS is unpredictable.
-     * If this instance is a {@code MapLayer} the envelope is the resource data envelope.
-     *
-     * @return the spatiotemporal extent. May be absent if none or too costly to compute.
-     * @throws DataStoreException if an error occurred while reading or computing the envelope.
-     */
-    @Override
-    public Optional<Envelope> getEnvelope() throws DataStoreException {
-        List<Envelope> envelopes = new ArrayList<>();
-        for (MapItem i : components) {
-            i.getEnvelope().ifPresent(envelopes::add);
-        }
-        switch (envelopes.size()) {
-            case 0 : return Optional.empty();
-            case 1 : return Optional.of(envelopes.get(0));
-            default : {
-                try {
-                    return Optional.ofNullable(Envelopes.union(envelopes.toArray(new Envelope[envelopes.size()])));
-                } catch (TransformException ex) {
-                    throw new DataStoreException(ex.getMessage(), ex);
-                }
-            }
-        }
-    }
-}
diff --git a/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockFeatureTypeStyle.java b/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockFeatureTypeStyle.java
deleted file mode 100644
index fdd5cf3..0000000
--- a/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockFeatureTypeStyle.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.map;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import org.opengis.filter.ResourceId;
-import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.style.Description;
-import org.opengis.style.FeatureTypeStyle;
-import org.opengis.style.Rule;
-import org.opengis.style.SemanticType;
-import org.opengis.style.StyleVisitor;
-import org.opengis.util.GenericName;
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class MockFeatureTypeStyle implements FeatureTypeStyle {
-
-    private String name;
-    private Description description;
-    private final Set<GenericName> featureTypeNames = new HashSet<>();
-    private final Set<SemanticType> semanticTypes = new HashSet<>();
-    private final List<Rule> rules = new ArrayList<>();
-    private OnlineResource onlineResource;
-
-    @Override
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    @Override
-    public Description getDescription() {
-        return description;
-    }
-
-    public void setDescription(Description description) {
-        this.description = description;
-    }
-
-    @Override
-    public Set<GenericName> featureTypeNames() {
-        return featureTypeNames;
-    }
-
-    @Override
-    public Set<SemanticType> semanticTypeIdentifiers() {
-        return semanticTypes;
-    }
-
-    @Override
-    public List<Rule> rules() {
-        return rules;
-    }
-
-    @Override
-    public OnlineResource getOnlineResource() {
-        return onlineResource;
-    }
-
-    public void setOnlineResource(OnlineResource onlineResource) {
-        this.onlineResource = onlineResource;
-    }
-
-    /**
-     * May be removed from GeoAPI.
-     */
-    @Override
-    @Deprecated
-    public ResourceId getFeatureInstanceIDs() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * May be removed from GeoAPI.
-     */
-    @Override
-    @Deprecated
-    public Object accept(StyleVisitor visitor, Object extraData) {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockLineSymbolizer.java b/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockLineSymbolizer.java
deleted file mode 100644
index f9df4e7..0000000
--- a/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockLineSymbolizer.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.map;
-
-import javax.measure.Unit;
-import javax.measure.quantity.Length;
-import org.opengis.feature.Feature;
-import org.opengis.filter.Expression;
-import org.opengis.style.Description;
-import org.opengis.style.LineSymbolizer;
-import org.opengis.style.Stroke;
-import org.opengis.style.StyleVisitor;
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class MockLineSymbolizer implements LineSymbolizer {
-
-    String name;
-    Description description;
-
-    Stroke stroke;
-    Expression<Feature,?> perpendicularOffset;
-    Unit<Length> unitOfMeasure;
-    Expression<Feature,?> geometry;
-
-    @Override
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    @Override
-    public Description getDescription() {
-        return description;
-    }
-
-    public void setDescription(Description description) {
-        this.description = description;
-    }
-
-    @Override
-    public Stroke getStroke() {
-        return stroke;
-    }
-
-    public void setStroke(Stroke stroke) {
-        this.stroke = stroke;
-    }
-
-    @Override
-    public Expression getPerpendicularOffset() {
-        return perpendicularOffset;
-    }
-
-    public void setPerpendicularOffset(Expression perpendicularOffset) {
-        this.perpendicularOffset = perpendicularOffset;
-    }
-
-    @Override
-    public Unit<Length> getUnitOfMeasure() {
-        return unitOfMeasure;
-    }
-
-    public void setUnitOfMeasure(Unit<Length> unitOfMeasure) {
-        this.unitOfMeasure = unitOfMeasure;
-    }
-
-    @Override
-    public Expression<Feature,?> getGeometry() {
-        return geometry;
-    }
-
-    public void setGeometry(Expression geometry) {
-        this.geometry = geometry;
-    }
-
-    /**
-     * Will likely be removed from geoapi.
-     */
-    @Deprecated
-    @Override
-    public String getGeometryPropertyName() {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Will likely be removed from geoapi.
-     */
-    @Deprecated
-    @Override
-    public Object accept(StyleVisitor sv, Object o) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-}
diff --git a/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockRule.java b/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockRule.java
deleted file mode 100644
index f3eecee..0000000
--- a/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockRule.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.map;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.opengis.filter.Filter;
-import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.style.Description;
-import org.opengis.style.GraphicLegend;
-import org.opengis.style.Rule;
-import org.opengis.style.StyleVisitor;
-import org.opengis.style.Symbolizer;
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class MockRule implements Rule {
-
-    private String name;
-    private Description description;
-    private GraphicLegend legend;
-    private Filter filter;
-    private boolean iselseFilter;
-    private double minScaleDenominator = 0.0;
-    private double maxScaleDenominator = Double.MAX_VALUE;
-    private final List<Symbolizer> symbolizers = new ArrayList<>();
-    private OnlineResource onlineResource;
-
-    @Override
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    @Override
-    public Description getDescription() {
-        return description;
-    }
-
-    public void setDescription(Description description) {
-        this.description = description;
-    }
-
-    @Override
-    public GraphicLegend getLegend() {
-        return legend;
-    }
-
-    public void setLegend(GraphicLegend legend) {
-        this.legend = legend;
-    }
-
-    @Override
-    public Filter getFilter() {
-        return filter;
-    }
-
-    public void setFilter(Filter filter) {
-        this.filter = filter;
-    }
-
-    @Override
-    public boolean isElseFilter() {
-        return iselseFilter;
-    }
-
-    public void setIsElseFilter(boolean iselseFilter) {
-        this.iselseFilter = iselseFilter;
-    }
-
-    @Override
-    public double getMinScaleDenominator() {
-        return minScaleDenominator;
-    }
-
-    public void setMinScaleDenominator(double minScaleDenominator) {
-        this.minScaleDenominator = minScaleDenominator;
-    }
-
-    @Override
-    public double getMaxScaleDenominator() {
-        return maxScaleDenominator;
-    }
-
-    public void setMaxScaleDenominator(double maxScaleDenominator) {
-        this.maxScaleDenominator = maxScaleDenominator;
-    }
-
-    @Override
-    public List<Symbolizer> symbolizers() {
-        return symbolizers;
-    }
-
-    @Override
-    public OnlineResource getOnlineResource() {
-        return onlineResource;
-    }
-
-    public void setOnlineResource(OnlineResource onlineResource) {
-        this.onlineResource = onlineResource;
-    }
-
-    /**
-     * Will likely be removed from geoapi.
-     */
-    @Deprecated
-    @Override
-    public Object accept(StyleVisitor sv, Object o) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-}
diff --git a/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockStyle.java b/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockStyle.java
deleted file mode 100644
index 2109949..0000000
--- a/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/MockStyle.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.map;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.opengis.style.Description;
-import org.opengis.style.FeatureTypeStyle;
-import org.opengis.style.StyleVisitor;
-import org.opengis.style.Symbolizer;
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public final class MockStyle implements org.opengis.style.Style {
-
-    private String name;
-    private Description description;
-    private final List<FeatureTypeStyle> fts = new ArrayList<>();
-
-    @Override
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    @Override
-    public Description getDescription() {
-        return description;
-    }
-
-    public void setDescription(Description description) {
-        this.description = description;
-    }
-
-    @Override
-    public List<FeatureTypeStyle> featureTypeStyles() {
-        return fts;
-    }
-
-    /**
-     * Will likely be removed from geoapi.
-     */
-    @Deprecated
-    @Override
-    public boolean isDefault() {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Will likely be removed from geoapi.
-     */
-    @Deprecated
-    @Override
-    public Symbolizer getDefaultSpecification() {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    /**
-     * Will likely be removed from geoapi.
-     */
-    @Deprecated
-    @Override
-    public Object accept(StyleVisitor sv, Object o) {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-}
diff --git a/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/SEPortrayerTest.java b/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/SEPortrayerTest.java
deleted file mode 100644
index a946955..0000000
--- a/core/sis-portrayal/src/test/java/org/apache/sis/internal/map/SEPortrayerTest.java
+++ /dev/null
@@ -1,774 +0,0 @@
-/*
- * 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.map;
-
-import java.util.Arrays;
-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;
-import java.util.stream.Stream;
-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.feature.builder.AttributeRole;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.internal.feature.AttributeConvention;
-import org.apache.sis.internal.storage.MemoryFeatureSet;
-import org.apache.sis.storage.FeatureQuery;
-import org.apache.sis.portrayal.MapItem;
-import org.apache.sis.portrayal.MapLayer;
-import org.apache.sis.portrayal.MapLayers;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.CommonCRS;
-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.event.StoreEvent;
-import org.apache.sis.storage.event.StoreListener;
-import org.apache.sis.test.TestCase;
-import org.apache.sis.util.iso.Names;
-import static org.junit.Assert.*;
-import org.junit.Test;
-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.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.MatchAction;
-import org.opengis.geometry.Envelope;
-import org.opengis.metadata.Metadata;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.style.SemanticType;
-import org.opengis.style.Symbolizer;
-import org.opengis.util.GenericName;
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- */
-public class SEPortrayerTest extends TestCase {
-
-    private final FilterFactory<Feature,Object,Object> filterFactory;
-    private final FeatureSet fishes;
-    private final FeatureSet boats;
-
-    public SEPortrayerTest() {
-        filterFactory = DefaultFilterFactory.forFeatures();
-
-        final GeometryFactory gf = org.apache.sis.internal.feature.jts.Factory.INSTANCE.factory(false);
-        final CoordinateReferenceSystem crs = CommonCRS.WGS84.normalizedGeographic();
-
-        final FeatureTypeBuilder fishbuilder = new FeatureTypeBuilder();
-        fishbuilder.setName("fish");
-        fishbuilder.addAttribute(String.class).setName("id").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        fishbuilder.addAttribute(Point.class).setCRS(crs).setName("geom").addRole(AttributeRole.DEFAULT_GEOMETRY);
-        fishbuilder.addAttribute(String.class).setName("description");
-        final FeatureType fishType = fishbuilder.build();
-
-        final Point point1 = gf.createPoint(new CoordinateXY(0, 0));
-        point1.setUserData(crs);
-        final Feature fish1 = fishType.newInstance();
-        fish1.setPropertyValue("id", "1");
-        fish1.setPropertyValue("geom", point1);
-        fish1.setPropertyValue("description", "A red fish");
-
-        final Point point2 = gf.createPoint(new CoordinateXY(10, 20));
-        point2.setUserData(crs);
-        final Feature fish2 = fishType.newInstance();
-        fish2.setPropertyValue("id", "2");
-        fish2.setPropertyValue("geom", point2);
-        fish2.setPropertyValue("description", "A small blue fish");
-
-        //a special fish with a sub-type
-        final FeatureTypeBuilder sharkbuilder = new FeatureTypeBuilder();
-        sharkbuilder.setName("shark");
-        sharkbuilder.setSuperTypes(fishType);
-        sharkbuilder.addAttribute(String.class).setName("specie");
-        sharkbuilder.addAttribute(Double.class).setName("length");
-        final FeatureType sharkType = sharkbuilder.build();
-
-        final Point point3 = gf.createPoint(new CoordinateXY(30, 40));
-        point3.setUserData(crs);
-        final Feature shark1 = sharkType.newInstance();
-        shark1.setPropertyValue("id", "100");
-        shark1.setPropertyValue("geom", point3);
-        shark1.setPropertyValue("description", "dangerous fish");
-        shark1.setPropertyValue("specie", "White Shark");
-        shark1.setPropertyValue("length", 12.0);
-
-        fishes = new MemoryFeatureSet(null, sharkType, Arrays.asList(fish1, fish2, shark1));
-
-        final FeatureTypeBuilder boatbuilder = new FeatureTypeBuilder();
-        boatbuilder.setName("boat");
-        boatbuilder.addAttribute(String.class).setName("id").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        boatbuilder.addAttribute(Polygon.class).setCRS(crs).setName("geom").addRole(AttributeRole.DEFAULT_GEOMETRY);
-        boatbuilder.addAttribute(String.class).setName("description");
-        final FeatureType boatType = boatbuilder.build();
-
-        final Polygon poly1 = gf.createPolygon(gf.createLinearRing(new CoordinateXY[] {
-            new CoordinateXY(0, 0), new CoordinateXY(0, 1), new CoordinateXY(1, 1), new CoordinateXY(0, 0)}));
-        poly1.setUserData(crs);
-        final Feature boat1 = boatType.newInstance();
-        boat1.setPropertyValue("id", "10");
-        boat1.setPropertyValue("geom", poly1);
-        boat1.setPropertyValue("description", "A fishing boat");
-
-        final Polygon poly2 = gf.createPolygon(gf.createLinearRing(new CoordinateXY[] {
-            new CoordinateXY(0, 0), new CoordinateXY(0, 1), new CoordinateXY(1, 1), new CoordinateXY(0, 0)}));
-        poly2.setUserData(crs);
-        final Feature boat2 = boatType.newInstance();
-        boat2.setPropertyValue("id", "20");
-        boat2.setPropertyValue("geom", poly2);
-        boat2.setPropertyValue("description", "A submarine");
-
-        boats = new MemoryFeatureSet(null, boatType, Arrays.asList(boat1, boat2));
-    }
-
-    private Set<Match> present(MapItem item) {
-        return present(item, CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()));
-    }
-
-    private Set<Match> present(MapItem item, Envelope env) {
-        final GridGeometry grid = new GridGeometry(new GridExtent(360, 180), env, GridOrientation.REFLECTION_Y);
-        final SEPortrayer portrayer = new SEPortrayer();
-        final Stream<Presentation> stream = portrayer.present(grid, item);
-        final List<Presentation> presentations = stream.collect(Collectors.toList());
-
-        final Set<Match> ids = new HashSet<>();
-        presentations.stream().forEach(new Consumer<Presentation>() {
-            @Override
-            public void accept(Presentation t) {
-                if (t instanceof SEPresentation) {
-                    SEPresentation se = (SEPresentation) t;
-                    Feature Feature = se.getCandidate();
-                    ids.add(new Match(String.valueOf(Feature.getPropertyValue(AttributeConvention.IDENTIFIER)),
-                            se.getLayer(),
-                            se.getResource(),
-                            se.getSymbolizer()));
-                } else if (t instanceof ExceptionPresentation) {
-                    final ExceptionPresentation ep = (ExceptionPresentation) t;
-                    ids.add(new Match(ep.getException()));
-                }
-            }
-        });
-        return ids;
-    }
-
-    /**
-     * Portray using no filtering operations
-     */
-    @Test
-    public void testSanity() {
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        final MockRule rule = new MockRule();
-        final MockLineSymbolizer symbolizer = new MockLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(5, presentations.size());
-        assertTrue(presentations.contains(new Match("1", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("100", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("10", boatLayer, boats, symbolizer)));
-        assertTrue(presentations.contains(new Match("20", boatLayer, boats, symbolizer)));
-    }
-
-    /**
-     * Test portrayer includes the bounding box of the canvas while querying features.
-     * Only fish feature with identifier "2" matches in this test.
-     */
-    @Test
-    public void testCanvasBboxfilter() {
-        final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
-        env.setRange(0, 9, 11);
-        env.setRange(1, 19, 21);
-
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        final MockRule rule = new MockRule();
-        final MockLineSymbolizer symbolizer = new MockLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers, env);
-        assertEquals(1, presentations.size());
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizer)));
-    }
-
-    /**
-     * Test portrayer uses the user defined query when portraying.
-     * Only fish feature with identifier "1" and boat feature with identifier "20" matches in this test.
-     */
-    @Test
-    public void testUserQuery() {
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        final MockRule rule = new MockRule();
-        final MockLineSymbolizer symbolizer = new MockLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final Filter<Feature> filter = filterFactory.or(
-                filterFactory.resourceId("1"),
-                filterFactory.resourceId("20"));
-        final FeatureQuery query = new FeatureQuery();
-        query.setSelection(filter);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        fishLayer.setQuery(query);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        boatLayer.setQuery(query);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(2, presentations.size());
-        assertTrue(presentations.contains(new Match("1", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("20", boatLayer, boats, symbolizer)));
-    }
-
-    /**
-     * Portray using defined type names.
-     * Test expect only boat type features to be rendered.
-     */
-    @Test
-    public void testFeatureTypeStyleTypeNames() {
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        fts.featureTypeNames().add(Names.createLocalName(null, null, "boat"));
-        final MockRule rule = new MockRule();
-        final MockLineSymbolizer symbolizer = new MockLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(2, presentations.size());
-        assertTrue(presentations.contains(new Match("10", boatLayer, boats, symbolizer)));
-        assertTrue(presentations.contains(new Match("20", boatLayer, boats, symbolizer)));
-    }
-
-    /**
-     * Portray using defined type names.
-     * Test expect only point geometric type to be rendered.
-     */
-    @Test
-    public void testFeatureTypeStyleSemanticType() {
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        fts.semanticTypeIdentifiers().add(SemanticType.POINT);
-        final MockRule rule = new MockRule();
-        final MockLineSymbolizer symbolizer = new MockLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(3, presentations.size());
-        assertTrue(presentations.contains(new Match("1", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizer)));
-        assertTrue(presentations.contains(new Match("100", fishLayer, fishes, symbolizer)));
-    }
-
-    /**
-     * Portray using defined rule filter
-     * Test expect only features with identifier equals "2" to match.
-     */
-    @Test
-    public void testRuleFilter() {
-        final Filter<Feature> filter = filterFactory.resourceId("2");
-
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        final MockRule rule = new MockRule();
-        rule.setFilter(filter);
-        final MockLineSymbolizer symbolizer = new MockLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(1, presentations.size());
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizer)));
-    }
-
-    /**
-     * Portray using defined rule scale filter.
-     * Test expect only matching scale rule symbolizer to be portrayed.
-     */
-    @Test
-    public void testRuleScale() {
-        final MockLineSymbolizer symbolizerAbove = new MockLineSymbolizer();
-        final MockLineSymbolizer symbolizerUnder = new MockLineSymbolizer();
-        final MockLineSymbolizer symbolizerMatch = new MockLineSymbolizer();
-
-        //Symbology rendering scale here is 3.944391406060875E8
-        final MockRule ruleAbove = new MockRule();
-        ruleAbove.symbolizers().add(symbolizerAbove);
-        ruleAbove.setMinScaleDenominator(4e8);
-        ruleAbove.setMaxScaleDenominator(Double.MAX_VALUE);
-        final MockRule ruleUnder = new MockRule();
-        ruleUnder.symbolizers().add(symbolizerUnder);
-        ruleUnder.setMinScaleDenominator(0.0);
-        ruleUnder.setMaxScaleDenominator(3e8);
-        final MockRule ruleMatch = new MockRule();
-        ruleMatch.symbolizers().add(symbolizerMatch);
-        ruleMatch.setMinScaleDenominator(3e8);
-        ruleMatch.setMaxScaleDenominator(4e8);
-
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(ruleAbove);
-        fts.rules().add(ruleUnder);
-        fts.rules().add(ruleMatch);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(5, presentations.size());
-        assertTrue(presentations.contains(new Match("1", fishLayer, fishes, symbolizerMatch)));
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizerMatch)));
-        assertTrue(presentations.contains(new Match("100", fishLayer, fishes, symbolizerMatch)));
-        assertTrue(presentations.contains(new Match("10", boatLayer, boats, symbolizerMatch)));
-        assertTrue(presentations.contains(new Match("20", boatLayer, boats, symbolizerMatch)));
-    }
-
-    /**
-     * Portray using defined rule filter.
-     * The rule uses a property only available on the shark sub type.
-     * Test expect only features with specy equals "White Shark" to match.
-     */
-    @Test
-    public void testRuleFilterOnSubType() {
-        final BinaryComparisonOperator<Feature> filter = filterFactory.equal(
-                filterFactory.property("specie", String.class),
-                filterFactory.literal("White Shark"),
-                true, MatchAction.ANY);
-
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        final MockRule rule = new MockRule();
-        rule.setFilter(filter);
-        final MockLineSymbolizer symbolizer = new MockLineSymbolizer();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-        rule.symbolizers().add(symbolizer);
-
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(1, presentations.size());
-        assertTrue(presentations.contains(new Match("100", fishLayer, fishes, symbolizer)));
-    }
-
-    /**
-     * Portray using defined rule 'is else' property.
-     * Test expect only feature with identifier "10" to be rendered with the base rule
-     * and other features to rendered with the fallback rule.
-     */
-    @Test
-    public void testRuleElseCondition() {
-        final Filter<Feature> filter = filterFactory.resourceId("10");
-
-        final MockLineSymbolizer symbolizerBase = new MockLineSymbolizer();
-        final MockLineSymbolizer symbolizerElse = new MockLineSymbolizer();
-
-        final MockRule ruleBase = new MockRule();
-        ruleBase.symbolizers().add(symbolizerBase);
-        ruleBase.setFilter(filter);
-        final MockRule ruleOther = new MockRule();
-        ruleOther.setIsElseFilter(true);
-        ruleOther.symbolizers().add(symbolizerElse);
-
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(ruleBase);
-        fts.rules().add(ruleOther);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(5, presentations.size());
-        assertTrue(presentations.contains(new Match("1", fishLayer, fishes, symbolizerElse)));
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizerElse)));
-        assertTrue(presentations.contains(new Match("100", fishLayer, fishes, symbolizerElse)));
-        assertTrue(presentations.contains(new Match("10", boatLayer, boats, symbolizerBase)));
-        assertTrue(presentations.contains(new Match("20", boatLayer, boats, symbolizerElse)));
-    }
-
-    /**
-     * Portray using and aggregated resource.
-     * Test expect presentations to be correctly associated to each resource but on the same layer.
-     */
-    @Test
-    public void testAggregateResource() {
-        final MockLineSymbolizer symbolizerBase = new MockLineSymbolizer();
-
-        final MockRule ruleBase = new MockRule();
-        ruleBase.symbolizers().add(symbolizerBase);
-
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(ruleBase);
-
-        final List<Resource> list = Arrays.asList(fishes, boats);
-        final Aggregate agg = new Aggregate() {
-            @Override
-            public Collection<? extends Resource> components() throws DataStoreException {
-                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();
-        aggLayer.setData(agg);
-        aggLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(aggLayer);
-
-        final Set<Match> presentations = present(layers);
-        assertEquals(5, presentations.size());
-        assertTrue(presentations.contains(new Match("1", aggLayer, fishes, symbolizerBase)));
-        assertTrue(presentations.contains(new Match("2", aggLayer, fishes, symbolizerBase)));
-        assertTrue(presentations.contains(new Match("100", aggLayer, fishes, symbolizerBase)));
-        assertTrue(presentations.contains(new Match("10", aggLayer, boats, symbolizerBase)));
-        assertTrue(presentations.contains(new Match("20", aggLayer, boats, symbolizerBase)));
-    }
-
-    /**
-     * Portray preserving all feature attributes test.
-     */
-    @Test
-    public void testPreserveProperties() {
-        final Filter<Feature> filter = filterFactory.resourceId("2");
-        final MockLineSymbolizer symbolizer = new MockLineSymbolizer();
-
-        final MockRule rule = new MockRule();
-        rule.symbolizers().add(symbolizer);
-        rule.setFilter(filter);
-
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-
-        final GridGeometry grid = new GridGeometry(new GridExtent(360, 180), CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()), GridOrientation.REFLECTION_Y);
-        {
-            // test without preserve properties
-            // we expect only identifier and geometry to be available.
-            final SEPortrayer portrayer = new SEPortrayer();
-            portrayer.setPreserveProperties(false);
-            final Stream<Presentation> stream = portrayer.present(grid, layers);
-            final List<Presentation> presentations = stream.collect(Collectors.toList());
-            assertEquals(1, presentations.size());
-            final SEPresentation presentation = (SEPresentation) presentations.get(0);
-            final Feature feature = presentation.getCandidate();
-            final FeatureType type = feature.getType();
-            assertEquals(2, type.getProperties(true).size());
-            assertNotNull(type.getProperty(AttributeConvention.IDENTIFIER));
-            assertNotNull(type.getProperty(AttributeConvention.GEOMETRY));
-        }
-        {
-            // test with preserve properties
-            // we expect only identifier and geometry to be available.
-            final SEPortrayer portrayer = new SEPortrayer();
-            portrayer.setPreserveProperties(true);
-            final Stream<Presentation> stream = portrayer.present(grid, layers);
-            final List<Presentation> presentations = stream.collect(Collectors.toList());
-            assertEquals(1, presentations.size());
-            final SEPresentation presentation = (SEPresentation) presentations.get(0);
-            final Feature feature = presentation.getCandidate();
-            final FeatureType type = feature.getType();
-            assertEquals(6, type.getProperties(true).size());
-            assertNotNull(type.getProperty(AttributeConvention.IDENTIFIER));
-            assertNotNull(type.getProperty(AttributeConvention.GEOMETRY));
-            assertNotNull(type.getProperty(AttributeConvention.ENVELOPE));
-            assertNotNull(type.getProperty("id"));
-            assertNotNull(type.getProperty("geom"));
-            assertNotNull(type.getProperty("description"));
-        }
-    }
-
-    /**
-     * Test all properties used in the style are returned
-     * in the presentation features.
-     */
-    @Test
-    public void testStylePropertiesReturned() {
-        final BinaryComparisonOperator<Feature> filter = filterFactory.equal(
-                filterFactory.property("id", String.class),
-                filterFactory.literal("2"),
-                true, MatchAction.ANY);
-
-        final MockLineSymbolizer symbolizer = new MockLineSymbolizer();
-        symbolizer.perpendicularOffset = filterFactory.property("description", String.class);
-
-        final MockRule rule = new MockRule();
-        rule.symbolizers().add(symbolizer);
-        rule.setFilter(filter);
-
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-
-        final GridGeometry grid = new GridGeometry(new GridExtent(360, 180), CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()), GridOrientation.REFLECTION_Y);
-
-        // test without preserve properties
-        // we expect identifier, geometry, id(used by rule filter), description (used in symbolizer)
-        final SEPortrayer portrayer = new SEPortrayer();
-        portrayer.setPreserveProperties(false);
-        final Stream<Presentation> stream = portrayer.present(grid, layers);
-        final List<Presentation> presentations = stream.collect(Collectors.toList());
-        assertEquals(1, presentations.size());
-        final SEPresentation presentation = (SEPresentation) presentations.get(0);
-        final Feature feature = presentation.getCandidate();
-        final FeatureType type = feature.getType();
-        assertEquals(4, type.getProperties(true).size());
-        assertNotNull(type.getProperty(AttributeConvention.IDENTIFIER));
-        assertNotNull(type.getProperty(AttributeConvention.GEOMETRY));
-        assertNotNull(type.getProperty("id"));
-        assertNotNull(type.getProperty("description"));
-    }
-
-    /**
-     * Test a geometry expression do not affect portraying bbox filtering.
-     */
-    @Test
-    public void testGeometryExpression() {
-        final MockLineSymbolizer symbolizer = new MockLineSymbolizer();
-        symbolizer.geometry = filterFactory.function("ST_Centroid", filterFactory.property("geom"));
-
-        final MockRule rule = new MockRule();
-        rule.symbolizers().add(symbolizer);
-
-        final MockStyle style = new MockStyle();
-        final MockFeatureTypeStyle fts = new MockFeatureTypeStyle();
-        style.featureTypeStyles().add(fts);
-        fts.rules().add(rule);
-
-        final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
-        env.setRange(0, 9, 11);
-        env.setRange(1, 19, 21);
-
-        final MapLayer fishLayer = new MapLayer();
-        fishLayer.setData(fishes);
-        fishLayer.setStyle(style);
-        final MapLayer boatLayer = new MapLayer();
-        boatLayer.setData(boats);
-        boatLayer.setStyle(style);
-        final MapLayers layers = new MapLayers();
-        layers.getComponents().add(fishLayer);
-        layers.getComponents().add(boatLayer);
-
-        final Set<Match> presentations = present(layers, env);
-        assertEquals(1, presentations.size());
-        assertTrue(presentations.contains(new Match("2", fishLayer, fishes, symbolizer)));
-    }
-
-    private static class Match {
-        private final String identifier;
-        private final MapLayer layer;
-        private final Resource resource;
-        private final Symbolizer symbolizer;
-        private final Exception exception;
-
-        public Match(String identifier, MapLayer layer, Resource resource, Symbolizer symbolizer) {
-            this.identifier = identifier;
-            this.layer = layer;
-            this.resource = resource;
-            this.symbolizer = symbolizer;
-            this.exception = null;
-        }
-
-        public Match(Exception e) {
-            this.identifier = null;
-            this.layer = null;
-            this.resource = null;
-            this.symbolizer = null;
-            this.exception = e;
-        }
-
-        @Override
-        public int hashCode() {
-            int hash = 7;
-            hash = 29 * hash + Objects.hashCode(this.identifier);
-            hash = 29 * hash + Objects.hashCode(this.layer);
-            hash = 29 * hash + Objects.hashCode(this.resource);
-            hash = 29 * hash + Objects.hashCode(this.symbolizer);
-            hash = 29 * hash + Objects.hashCode(this.exception);
-            return hash;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null) {
-                return false;
-            }
-            if (getClass() != obj.getClass()) {
-                return false;
-            }
-            final Match other = (Match) obj;
-            if (!Objects.equals(this.identifier, other.identifier)) {
-                return false;
-            }
-            if (!Objects.equals(this.layer, other.layer)) {
-                return false;
-            }
-            if (!Objects.equals(this.resource, other.resource)) {
-                return false;
-            }
-            if (!Objects.equals(this.symbolizer, other.symbolizer)) {
-                return false;
-            }
-            if (!Objects.equals(this.exception, other.exception)) {
-                return false;
-            }
-            return true;
-        }
-    }
-}
diff --git a/core/sis-portrayal/src/test/java/org/apache/sis/portrayal/MapLayersTest.java b/core/sis-portrayal/src/test/java/org/apache/sis/portrayal/MapLayersTest.java
deleted file mode 100644
index 3276f1f..0000000
--- a/core/sis-portrayal/src/test/java/org/apache/sis/portrayal/MapLayersTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.portrayal;
-
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.sis.internal.map.ListChangeEvent;
-import org.apache.sis.measure.NumberRange;
-import org.apache.sis.test.TestCase;
-import static org.junit.Assert.*;
-import org.junit.Test;
-
-/**
- *
- * @author Johann Sorel (Geomatys)
- * @version 1.1
- * @since 1.1
- */
-public class MapLayersTest extends TestCase {
-
-    /**
-     * Test the maplayers components list events.
-     */
-    @Test
-    public void testListEvents() {
-
-        final MapLayers layers = new MapLayers();
-        final MapLayer layer1 = new MapLayer();
-        final MapLayer layer2 = new MapLayer();
-
-        final AtomicInteger eventNum = new AtomicInteger();
-        layers.addPropertyChangeListener(MapLayers.COMPONENTS_PROPERTY, new PropertyChangeListener() {
-            @Override
-            public void propertyChange(PropertyChangeEvent evt) {
-                assertTrue(evt instanceof ListChangeEvent);
-                final ListChangeEvent levt = (ListChangeEvent) evt;
-                assertEquals(layers.getComponents(), levt.getOldValue());
-                assertEquals(layers.getComponents(), levt.getNewValue());
-                assertEquals(MapLayers.COMPONENTS_PROPERTY, levt.getPropertyName());
-                assertEquals(layers, levt.getSource());
-                int eventId = eventNum.incrementAndGet();
-                switch (eventId) {
-                    case 1 :
-                        assertEquals(ListChangeEvent.Type.ADDED, levt.getType());
-                        assertEquals(NumberRange.create(0, true, 0, true), levt.getRange());
-                        assertEquals(1, levt.getItems().size());
-                        assertEquals(layer1, levt.getItems().get(0));
-                        break;
-                    case 2 :
-                        assertEquals(ListChangeEvent.Type.ADDED, levt.getType());
-                        assertEquals(NumberRange.create(1, true, 1, true), levt.getRange());
-                        assertEquals(1, levt.getItems().size());
-                        assertEquals(layer2, levt.getItems().get(0));
-                        break;
-                    case 3 :
-                        assertEquals(ListChangeEvent.Type.REMOVED, levt.getType());
-                        assertEquals(NumberRange.create(1, true, 1, true), levt.getRange());
-                        assertEquals(1, levt.getItems().size());
-                        assertEquals(layer2, levt.getItems().get(0));
-                        break;
-                    case 4 :
-                        assertEquals(ListChangeEvent.Type.CHANGED, levt.getType());
-                        assertEquals(null, levt.getRange());
-                        assertEquals(null, levt.getItems());
-                        break;
-                }
-
-            }
-        });
-
-        layers.getComponents().add(layer1);
-        assertEquals(1, eventNum.get());
-
-        layers.getComponents().add(layer2);
-        assertEquals(2, eventNum.get());
-
-        layers.getComponents().remove(layer2);
-        assertEquals(3, eventNum.get());
-
-        layers.getComponents().set(0, layer2);
-        assertEquals(4, eventNum.get());
-    }
-
-}
diff --git a/core/sis-portrayal/src/test/java/org/apache/sis/test/suite/PortrayalTestSuite.java b/core/sis-portrayal/src/test/java/org/apache/sis/test/suite/PortrayalTestSuite.java
deleted file mode 100644
index 8824566..0000000
--- a/core/sis-portrayal/src/test/java/org/apache/sis/test/suite/PortrayalTestSuite.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.test.suite;
-
-import org.apache.sis.test.TestSuite;
-import org.junit.BeforeClass;
-import org.junit.runners.Suite;
-
-
-/**
- * All tests from the {@code sis-portrayal} module, in rough dependency order.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-@Suite.SuiteClasses({
-    org.apache.sis.portrayal.MapLayersTest.class,
-    org.apache.sis.internal.map.SEPortrayerTest.class,
-    org.apache.sis.internal.map.coverage.MultiResolutionCoverageLoaderTest.class
-})
-public final strictfp class PortrayalTestSuite extends TestSuite {
-    /**
-     * Verifies the list of tests before to run the suite.
-     * See {@link #verifyTestList(Class, Class[])} for more information.
-     */
-    @BeforeClass
-    public static void verifyTestList() {
-        assertNoMissingTest(PortrayalTestSuite.class);
-        verifyTestList(PortrayalTestSuite.class);
-    }
-}
diff --git a/core/sis-portrayal/src/test/java/org/apache/sis/test/suite/package-info.txt b/core/sis-portrayal/src/test/java/org/apache/sis/test/suite/package-info.txt
deleted file mode 100644
index ac895b5..0000000
--- a/core/sis-portrayal/src/test/java/org/apache/sis/test/suite/package-info.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Different modules provide classes in this package - be careful about collisions.
-This package is initially defined by the sis-utility module, which also provides
-the package-info.java file.
diff --git a/core/sis-referencing-by-identifiers/pom.xml b/core/sis-referencing-by-identifiers/pom.xml
index 78e1c0c..7770851 100644
--- a/core/sis-referencing-by-identifiers/pom.xml
+++ b/core/sis-referencing-by-identifiers/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
   <groupId>org.apache.sis.core</groupId>
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
index e8d02c7..cedbe55 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
@@ -29,9 +29,7 @@
 import org.apache.sis.geometry.GeneralDirectPosition;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -68,7 +66,7 @@
  * @since 0.8
  * @module
  */
-public abstract class AbstractLocation implements Location {
+public abstract class AbstractLocation {
     /**
      * The description of the nature of this geographic identifier, or {@code null} if unspecified.
      *
@@ -77,7 +75,7 @@
      *
      * @see #getLocationType()
      */
-    private LocationType type;
+    private AbstractLocationType type;
 
     /**
      * The geographic identifier, or {@code null} if unspecified.
@@ -90,10 +88,24 @@
      * Creates a new location for the given geographic identifier.
      * This constructor accepts {@code null} arguments, but this is not recommended.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of {@code type} argument may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param type        the description of the nature of this geographic identifier.
      * @param identifier  the geographic identifier to be returned by {@link #getGeographicIdentifier()}.
      */
-    protected AbstractLocation(final LocationType type, final CharSequence identifier) {
+    protected AbstractLocation(final ModifiableLocationType type, final CharSequence identifier) {
+        this.type       = type;
+        this.identifier = identifier;
+    }
+
+    /**
+     * Temporary workaround for the lack of {@code LocationType} interface and {@code AbstractLocationType}
+     * being package-private. We do not want to expose {@code AbstractLocationType} in public API for now.
+     */
+    AbstractLocation(final AbstractLocationType type, final CharSequence identifier) {
         this.type       = type;
         this.identifier = identifier;
     }
@@ -112,7 +124,7 @@
      * by the {@linkplain ModifiableLocationType#getIdentifications() location type identifications}.
      *
      * <div class="note"><b>Examples:</b>
-     * if {@link LocationType#getIdentifications()} contain “name”, then geographic identifiers may be country
+     * if {@code LocationType.getIdentifications()} contain “name”, then geographic identifiers may be country
      * names like “Japan” or “France”, or places like “Eiffel Tower”. If location type identifications contain
      * “code”, then geographic identifiers may be “SW1P 3AD” postcode.
      * </div>
@@ -125,7 +137,6 @@
      *
      * @see ModifiableLocationType#getIdentifications()
      */
-    @Override
     public InternationalString getGeographicIdentifier() {
         return Types.toInternationalString(identifier);
     }
@@ -136,7 +147,6 @@
      *
      * @return other identifier(s) for the location instance, or an empty collection if none.
      */
-    @Override
     public Collection<? extends InternationalString> getAlternativeGeographicIdentifiers() {
         return Collections.emptySet();
     }
@@ -147,7 +157,6 @@
      *
      * @return date of creation of this version of the location instance, or {@code null} if none.
      */
-    @Override
     public TemporalExtent getTemporalExtent() {
         return null;
     }
@@ -163,7 +172,6 @@
      * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox
      * @see org.apache.sis.metadata.iso.extent.DefaultBoundingPolygon
      */
-    @Override
     public GeographicExtent getGeographicExtent() {
         return null;
     }
@@ -177,7 +185,6 @@
      *
      * @return envelope that encompass the location, or {@code null} if none.
      */
-    @Override
     public Envelope getEnvelope() {
         final GeographicExtent extent = getGeographicExtent();
         return (extent instanceof GeographicBoundingBox) ? new Envelope2D((GeographicBoundingBox) extent) : null;
@@ -191,7 +198,6 @@
      *
      * @return coordinates of a representative point for the location instance, or {@code null} if none.
      */
-    @Override
     public Position getPosition() {
         final Envelope envelope = getEnvelope();
         if (envelope == null) {
@@ -209,10 +215,24 @@
     /**
      * Returns a description of the nature of this geographic identifier.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.
+     * If applied, this method will be made non-final.</div>
+     *
      * @return the nature of the identifier and its associated geographic location.
      */
-    @Override
-    public LocationType getLocationType() {
+    public final ModifiableLocationType getLocationType() {
+        return ModifiableLocationTypeAdapter.copy(type);
+    }
+
+    /**
+     * Workaround for the lack of {@code LocationType} interface in GeoAPI 3.0.
+     * This workaround will be removed in a future SIS version if the location
+     * type interface is introduced in a future GeoAPI version.
+     */
+    final AbstractLocationType type() {
         return type;
     }
 
@@ -220,14 +240,17 @@
      * Returns the organization responsible for defining the characteristics of the location instance.
      * The default implementation returns the {@linkplain ModifiableLocationType#getOwner() owner}.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @return organization responsible for defining the characteristics of the location instance, or {@code null}.
      *
      * @see ModifiableLocationType#getOwner()
      * @see ReferencingByIdentifiers#getOverallOwner()
      */
-    @Override
-    public Party getAdministrator() {
-        final LocationType type = getLocationType();
+    public AbstractParty getAdministrator() {
         return (type != null) ? type.getOwner() : null;
     }
 
@@ -235,12 +258,16 @@
      * Returns location instances of a different location type, for which this location instance is a sub-division.
      * The default implementation returns an empty list.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of collection elements may be generalized
+     * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return parent locations, or an empty collection if none.
      *
      * @see ModifiableLocationType#getParents()
      */
-    @Override
-    public Collection<? extends Location> getParents() {
+    public Collection<? extends AbstractLocation> getParents() {
         return Collections.emptyList();
     }
 
@@ -248,12 +275,16 @@
      * Returns location instances of a different location type which subdivides this location instance.
      * The default implementation returns an empty list.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of collection elements may be generalized
+     * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return child locations, or an empty collection if none.
      *
      * @see ModifiableLocationType#getChildren()
      */
-    @Override
-    public Collection<? extends Location> getChildren() {
+    public Collection<? extends AbstractLocation> getChildren() {
         return Collections.emptyList();
     }
 
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
index d7752b9..b8bdea3 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
@@ -31,8 +31,10 @@
 import org.apache.sis.util.Utilities;
 
 // Branch-dependent imports
-import org.opengis.referencing.gazetteer.LocationType;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+import java.util.Collection;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.extent.GeographicExtent;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -44,7 +46,7 @@
  * @since   0.8
  * @module
  */
-abstract class AbstractLocationType implements LocationType, LenientComparable {
+abstract class AbstractLocationType implements LenientComparable {
     /**
      * For sub-class constructors.
      */
@@ -65,11 +67,11 @@
      * @param  types  the location types for which to take a snapshot.
      * @return unmodifiable copies of the given location types.
      */
-    public static List<LocationType> snapshot(final ReferenceSystemUsingIdentifiers rs, final LocationType... types) {
+    public static List<AbstractLocationType> snapshot(final ReferencingByIdentifiers rs, final AbstractLocationType... types) {
         ArgumentChecks.ensureNonNull("types", types);
-        final List<LocationType> snapshot = FinalLocationType.snapshot(Arrays.asList(types), rs, new IdentityHashMap<>());
-        final Map<LocationType,Boolean> parents = new IdentityHashMap<>();
-        for (final LocationType type : snapshot) {
+        final List<AbstractLocationType> snapshot = FinalLocationType.snapshot(Arrays.asList(types), rs, new IdentityHashMap<>());
+        final Map<AbstractLocationType,Boolean> parents = new IdentityHashMap<>();
+        for (final AbstractLocationType type : snapshot) {
             checkForCycles(type, parents);
         }
         return snapshot;
@@ -80,11 +82,11 @@
      *
      * @throws IllegalArgumentException if an infinite recursivity is detected.
      */
-    private static void checkForCycles(final LocationType type, final Map<LocationType,Boolean> parents) {
+    private static void checkForCycles(final AbstractLocationType type, final Map<AbstractLocationType,Boolean> parents) {
         if (parents.put(type, Boolean.TRUE) != null) {
             throw new IllegalArgumentException(Resources.format(Resources.Keys.LocationTypeCycle_1, type.getName()));
         }
-        for (final LocationType child : type.getChildren()) {
+        for (final AbstractLocationType child : type.getChildren()) {
             checkForCycles(child, parents);
         }
         parents.remove(type);
@@ -101,6 +103,54 @@
     }
 
     /**
+     * Name of the location type.
+     */
+    public abstract InternationalString getName();
+
+    /**
+     * Property used as the defining characteristic of the location type.
+     */
+    public abstract InternationalString getTheme();
+
+    /**
+     * Method(s) of uniquely identifying location instances.
+     */
+    public abstract Collection<? extends InternationalString> getIdentifications();
+
+    /**
+     * The way in which location instances are defined.
+     */
+    public abstract InternationalString getDefinition();
+
+    /**
+     * Geographic area within which the location type occurs.
+     */
+    public abstract GeographicExtent getTerritoryOfUse();
+
+    /**
+     * The reference system that comprises this location type.
+     */
+    public abstract ReferencingByIdentifiers getReferenceSystem();
+
+    /**
+     * Name of organization or class of organization able to create and destroy location instances.
+     */
+    public abstract AbstractParty getOwner();
+
+    /**
+     * Parent location types (location types of which this location type is a sub-division).
+     * A location type can have more than one possible parent. For example the parent of a
+     * location type named <cite>“street”</cite> could be <cite>“locality”</cite>, <cite>“town”</cite>
+     * or <cite>“administrative area”</cite>.
+     */
+    public abstract Collection<? extends AbstractLocationType> getParents();
+
+    /**
+     * Child location types (location types which sub-divides this location type).
+     */
+    public abstract Collection<? extends AbstractLocationType> getChildren();
+
+    /**
      * Compares this location type with the specified object for equality.
      * This method compares the value of {@link #getName()} and {@link #getChildren()} in all modes.
      * At the opposite, values of {@link #getParents()} and {@link #getReferenceSystem()} are never
@@ -129,8 +179,8 @@
                 // Fall through
             }
             case BY_CONTRACT: {
-                if (!(object instanceof LocationType)) break;
-                final LocationType that = (LocationType) object;
+                if (!(object instanceof AbstractLocationType)) break;
+                final AbstractLocationType that = (AbstractLocationType) object;
                 // Do not compare the ReferenceSystem as it may cause an infinite recursion.
                 if (!Utilities.deepEquals(getTheme(),           that.getTheme(),           mode) ||
                     !Utilities.deepEquals(getIdentifications(), that.getIdentifications(), mode) ||
@@ -143,8 +193,8 @@
                 // Fall through
             }
             default: {
-                if (!(object instanceof LocationType)) break;
-                final LocationType that = (LocationType) object;
+                if (!(object instanceof AbstractLocationType)) break;
+                final AbstractLocationType that = (AbstractLocationType) object;
                 if (Objects.equals(getName(), that.getName())) {
                     /*
                      * To be safe, we should apply some check against infinite recursivity here.
@@ -186,7 +236,7 @@
     @Override
     public int hashCode() {
         int code = Objects.hashCode(getName());
-        for (final LocationType child : getChildren()) {
+        for (final AbstractLocationType child : getChildren()) {
             // Take only children name without recursivity over their own children.
             code = code*31 + Objects.hashCode(child.getName());
         }
@@ -222,10 +272,10 @@
      * on the assumption that subclasses verified this constraint by calls
      * to {@link #checkForCycles()}.
      */
-    private static void format(final LocationType type, final TreeTable.Node node) {
+    private static void format(final AbstractLocationType type, final TreeTable.Node node) {
         node.setValue(TableColumn.NAME, type.getName());
         node.setValue(TableColumn.VALUE_AS_TEXT, type.getDefinition());
-        for (final LocationType child : type.getChildren()) {
+        for (final AbstractLocationType child : type.getChildren()) {
             format(child, node.newChild());
         }
     }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java
index 47be601..2f9508a 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java
@@ -30,13 +30,11 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.LocationType;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
- * Unmodifiable description of a location created as a snapshot of another {@link LocationType} instance
+ * Unmodifiable description of a location created as a snapshot of another {@code LocationType} instance
  * at {@link ReferencingByIdentifiers} construction time. This instance will be set a different reference
  * system than the original location type.
  *
@@ -75,7 +73,7 @@
     /**
      * The reference system that comprises this location type.
      */
-    private final ReferenceSystemUsingIdentifiers referenceSystem;
+    private final ReferencingByIdentifiers referenceSystem;
 
     /**
      * Geographic area within which the location type occurs.
@@ -85,19 +83,19 @@
     /**
      * Name of organization or class of organization able to create and destroy location instances.
      */
-    private final Party owner;
+    private final AbstractParty owner;
 
     /**
      * Parent location types (location types of which this location type is a sub-division).
      * This list is unmodifiable.
      */
-    private final List<LocationType> parents;
+    private final List<AbstractLocationType> parents;
 
     /**
      * Child location types (location types which sub-divides this location type).
      * This list is unmodifiable.
      */
-    final List<LocationType> children;
+    final List<AbstractLocationType> children;
 
     /**
      * Creates a copy of the given location type with the reference system set to the given value.
@@ -107,8 +105,8 @@
      * @param existing  other {@code FinalLocationType} instances created before this one.
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
-    private FinalLocationType(final LocationType source, final ReferenceSystemUsingIdentifiers rs,
-            final Map<LocationType, FinalLocationType> existing)
+    private FinalLocationType(final AbstractLocationType source, final ReferencingByIdentifiers rs,
+            final Map<AbstractLocationType, FinalLocationType> existing)
     {
         /*
          * Put 'this' in the map at the beginning in case the parents and children contain cyclic references.
@@ -126,7 +124,7 @@
          */
         InternationalString theme;
         GeographicExtent    territoryOfUse;
-        Party               owner;
+        AbstractParty       owner;
         /*
          * Copy the value from the source location type, make them unmodifiable,
          * fallback on the ReferenceSystemUsingIdentifiers if necessary.
@@ -135,8 +133,8 @@
         theme           = source.getTheme();
         identifications = snapshot(source.getIdentifications());
         definition      = source.getDefinition();
-        territoryOfUse  = unmodifiable(GeographicExtent.class, source.getTerritoryOfUse());
-        owner           = unmodifiable(Party.class, source.getOwner());
+        territoryOfUse  = (GeographicExtent) unmodifiable(source.getTerritoryOfUse());
+        owner           = (AbstractParty) unmodifiable(source.getOwner());
         parents         = snapshot(source.getParents(),  rs, existing);
         children        = snapshot(source.getChildren(), rs, existing);
         referenceSystem = rs;
@@ -163,12 +161,12 @@
      * @param rs        the reference system to assign to the new location types.
      * @param existing  an initially empty identity hash map for internal usage by this method.
      */
-    static List<LocationType> snapshot(final Collection<? extends LocationType> types,
-            final ReferenceSystemUsingIdentifiers rs, final Map<LocationType, FinalLocationType> existing)
+    static List<AbstractLocationType> snapshot(final Collection<? extends AbstractLocationType> types,
+            final ReferencingByIdentifiers rs, final Map<AbstractLocationType, FinalLocationType> existing)
     {
-        final LocationType[] array = types.toArray(new LocationType[types.size()]);
+        final AbstractLocationType[] array = types.toArray(new AbstractLocationType[types.size()]);
         for (int i=0; i < array.length; i++) {
-            final LocationType source = array[i];
+            final AbstractLocationType source = array[i];
             ArgumentChecks.ensureNonNullElement("types", i, source);
             FinalLocationType copy = existing.get(source);
             if (copy == null) {
@@ -198,14 +196,12 @@
     /**
      * Returns an unmodifiable copy of the given metadata, if necessary and possible.
      *
-     * @param  <T>       compile-time value of the {@code type} argument.
-     * @param  type      the interface of the metadata object to eventually copy.
      * @param  metadata  the metadata object to eventually copy, or {@code null}.
      * @return an unmodifiable copy of the given metadata object, or {@code null} if the given argument is {@code null}.
      */
-    private static <T> T unmodifiable(final Class<T> type, T metadata) {
+    private static Object unmodifiable(Object metadata) {
         if (metadata instanceof ModifiableMetadata) {
-            metadata = MetadataCopier.forModifiable(((ModifiableMetadata) metadata).getStandard()).copy(type, metadata);
+            metadata = MetadataCopier.forModifiable(((ModifiableMetadata) metadata).getStandard()).copy(metadata);
             ((ModifiableMetadata) metadata).transitionTo(ModifiableMetadata.State.FINAL);
         }
         return metadata;
@@ -286,7 +282,7 @@
      * @return the reference system that comprises this location type.
      */
     @Override
-    public ReferenceSystemUsingIdentifiers getReferenceSystem() {
+    public ReferencingByIdentifiers getReferenceSystem() {
         return referenceSystem;
     }
 
@@ -296,7 +292,7 @@
      * @return organization or class of organization able to create and destroy location instances.
      */
     @Override
-    public Party getOwner() {
+    public AbstractParty getOwner() {
         return owner;
     }
 
@@ -309,7 +305,7 @@
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Because unmodifiable
-    public Collection<LocationType> getParents() {
+    public Collection<AbstractLocationType> getParents() {
         return parents;
     }
 
@@ -320,7 +316,7 @@
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Because unmodifiable
-    public Collection<LocationType> getChildren() {
+    public Collection<AbstractLocationType> getChildren() {
         return children;
     }
 }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
index 6740f00..2638ffe 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
@@ -45,10 +45,6 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 
-// Branch-dependent imports
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Geographic coordinates represented as <cite>geohashes</cite> strings.
@@ -199,10 +195,10 @@
      * ("Relax constraint on placement of this()/super() call in constructors").
      */
     @Workaround(library="JDK", version="1.8")
-    private static LocationType[] types() {
+    private static ModifiableLocationType[] types() {
         final ModifiableLocationType gzd = new ModifiableLocationType(IDENTIFIER);
         gzd.addIdentification(Vocabulary.formatInternational(Vocabulary.Keys.Code));
-        return new LocationType[] {gzd};
+        return new ModifiableLocationType[] {gzd};
     }
 
     /**
@@ -527,12 +523,17 @@
          * Decodes the given geohash into a latitude and a longitude.
          * The axis order depends on the coordinate reference system of the enclosing {@link GeohashReferenceSystem}.
          *
+         * <div class="warning"><b>Upcoming API change — generalization</b><br>
+         * in a future SIS version, the type of returned element may be generalized
+         * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+         * This change is pending GeoAPI revision.</div>
+         *
          * @param  geohash  geohash string to decode.
          * @return a new geographic coordinate for the given geohash.
          * @throws TransformException if an error occurred while parsing the given string.
          */
         @Override
-        public Location decode(final CharSequence geohash) throws TransformException {
+        public AbstractLocation decode(final CharSequence geohash) throws TransformException {
             ArgumentChecks.ensureNonEmpty("geohash", geohash);
             return new Decoder(geohash, coordinates);
         }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java
index 9ee1e7c..a4a351c 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java
@@ -61,13 +61,11 @@
 import org.apache.sis.util.resources.Vocabulary;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
- * Formats {@link Location} instances in a tabular format.
+ * Formats {@code Location} instances in a tabular format.
  * This format assumes a monospaced font and an encoding supporting drawing box characters (e.g. UTF-8).
  *
  * <div class="note"><b>Example:</b>
@@ -100,7 +98,7 @@
  * @since   0.8
  * @module
  */
-public class LocationFormat extends TabularFormat<Location> {
+public class LocationFormat extends TabularFormat<AbstractLocation> {
     /**
      * For cross-version compatibility.
      */
@@ -141,8 +139,8 @@
      * @return the type of values formatted by this {@code Format} instance.
      */
     @Override
-    public Class<Location> getValueType() {
-        return Location.class;
+    public Class<AbstractLocation> getValueType() {
+        return AbstractLocation.class;
     }
 
     /**
@@ -199,13 +197,18 @@
     /**
      * Writes a textual representation of the given location in the given stream or buffer.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of {@code location} parameter may be generalized
+     * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param  location    the location to format.
      * @param  toAppendTo  where to format the location.
      * @throws IOException if an error occurred while writing to the given appendable.
      */
     @Override
     @SuppressWarnings({"fallthrough", "null"})
-    public void format(final Location location, final Appendable toAppendTo) throws IOException {
+    public void format(final AbstractLocation location, final Appendable toAppendTo) throws IOException {
         ArgumentChecks.ensureNonNull("location", location);
         final Locale locale = getLocale(Locale.Category.DISPLAY);
         final Vocabulary vocabulary = Vocabulary.getResources(locale);
@@ -215,7 +218,7 @@
          * Location type.
          */
         table.appendHorizontalSeparator();
-        final LocationType type = location.getLocationType();
+        final AbstractLocationType type = location.type();
         if (type != null) {
             append(table, vocabulary, Vocabulary.Keys.LocationType, toString(type.getName(), locale));
         }
@@ -416,7 +419,7 @@
         /*
          * Organization responsible for defining the characteristics of the location instance.
          */
-        final Party administrator = location.getAdministrator();
+        final AbstractParty administrator = location.getAdministrator();
         if (administrator != null) {
             append(table, vocabulary, Vocabulary.Keys.Administrator, toString(administrator.getName(), locale));
         }
@@ -430,7 +433,7 @@
 
     /**
      * Creates the format to use for formatting a latitude, longitude or projected coordinate.
-     * This method is invoked by {@link #format(Location, Appendable)} when first needed.
+     * This method is invoked by {@code format(Location, Appendable)} when first needed.
      *
      * @param  valueType  {@code Angle.class}. {@code Number.class} or {@code Unit.class}.
      * @return a new {@link AngleFormat}, {@link NumberFormat} or {@link UnitFormat} instance
@@ -483,7 +486,7 @@
      * @throws ParseException if an error occurred while parsing the location.
      */
     @Override
-    public Location parse(CharSequence text, ParsePosition pos) throws ParseException {
+    public AbstractLocation parse(CharSequence text, ParsePosition pos) throws ParseException {
         throw new ParseException(Errors.format(Errors.Keys.UnsupportedOperation_1, "parse"), pos.getIndex());
     }
 
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
index 12ade70..d73cc6f 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
@@ -56,15 +56,12 @@
 import org.apache.sis.referencing.crs.DefaultProjectedCRS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.internal.referencing.j2d.IntervalRectangle;
-import org.apache.sis.metadata.sql.MetadataSource;
-import org.apache.sis.metadata.sql.MetadataStoreException;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.StringBuilders;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Workaround;
-import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.geometry.Shapes2D;
@@ -72,7 +69,6 @@
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.internal.referencing.Formulas;
-import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.measure.Longitude;
@@ -80,13 +76,10 @@
 import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
 
-import static java.util.logging.Logger.getLogger;
 
 // Branch-dependent imports
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 import org.apache.sis.internal.jdk9.JDK9;
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
 
 
 /**
@@ -302,16 +295,8 @@
      */
     @Workaround(library="JDK", version="1.8")
     private static Map<String,?> properties() {
-        Party party;
-        try {
-            party = MetadataSource.getProvided().lookup(Party.class, "{org}NATO");
-        } catch (MetadataStoreException e) {
-            party = null;
-            Logging.unexpectedException(getLogger(Modules.REFERENCING_BY_IDENTIFIERS),
-                    MilitaryGridReferenceSystem.class, "<init>", e);
-        }
-        NamedIdentifier name = new NamedIdentifier(null, "NATO", Resources.formatInternational(Resources.Keys.MGRS), null, null);
-        return properties(name, IDENTIFIER, party);
+        AbstractParty party = new AbstractParty("North Atlantic Treaty Organization", null);
+        return properties(new NamedIdentifier(null, "NATO", Resources.formatInternational(Resources.Keys.MGRS), null, null), IDENTIFIER, party);
     }
 
     /**
@@ -319,7 +304,7 @@
      * ("Relax constraint on placement of this()/super() call in constructors").
      */
     @Workaround(library="JDK", version="1.8")
-    private static LocationType[] types() {
+    private static ModifiableLocationType[] types() {
         final ModifiableLocationType gzd    = new ModifiableLocationType(Resources.formatInternational(Resources.Keys.GridZoneDesignator));
         final ModifiableLocationType square = new ModifiableLocationType(Resources.formatInternational(Resources.Keys.SquareIdentifier100));
         final ModifiableLocationType coord  = new ModifiableLocationType(Resources.formatInternational(Resources.Keys.GridCoordinates));
@@ -327,7 +312,7 @@
         coord .addIdentification(Vocabulary.formatInternational(Vocabulary.Keys.Coordinate));
         square.addParent(gzd);
         coord .addParent(square);
-        return new LocationType[] {gzd};
+        return new ModifiableLocationType[] {gzd};
     }
 
     /**
@@ -784,12 +769,17 @@
          * Decodes the given MGRS reference into a position and an envelope.
          * The Coordinate Reference System (CRS) associated to the returned position depends on the given reference.
          *
+         * <div class="warning"><b>Upcoming API change — generalization</b><br>
+         * in a future SIS version, the type of returned element may be generalized
+         * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+         * This change is pending GeoAPI revision.</div>
+         *
          * @param  reference  MGRS string to decode.
          * @return a new position with the longitude at coordinate 0 and latitude at coordinate 1.
          * @throws TransformException if an error occurred while parsing the given string.
          */
         @Override
-        public Location decode(final CharSequence reference) throws TransformException {
+        public AbstractLocation decode(final CharSequence reference) throws TransformException {
             ArgumentChecks.ensureNonEmpty("reference", reference);
             return new Decoder(this, reference);
         }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
index e0ea4a6..013f54d 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
@@ -33,8 +33,7 @@
 import org.apache.sis.util.iso.Types;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -144,7 +143,7 @@
     /**
      * Name of organization or class of organization able to create and destroy location instances.
      */
-    private Party owner;
+    private AbstractParty owner;
 
     /**
      * Parent location types (location types of which this location type is a sub-division).
@@ -354,6 +353,11 @@
      * If no organization has been explicitly set, then this method inherits the value from
      * the parents providing that all parents specify the same organization.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @return organization or class of organization able to create and destroy location instances,
      *         or {@code null} if no value has been defined or can be inherited.
      *
@@ -361,7 +365,7 @@
      * @see ReferencingByIdentifiers#getOverallOwner()
      */
     @Override
-    public Party getOwner() {
+    public AbstractParty getOwner() {
         return (owner != null) ? owner : inherit(ModifiableLocationType::getOwner);
     }
 
@@ -370,9 +374,14 @@
      * The given value is typically an instance of {@link DefaultOrganisation}.
      * For an alternative where only the organization name is specified, see {@link #setOwner(CharSequence)}.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the argument type may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @param  value  the new owner.
      */
-    public void setOwner(final Party value) {
+    public void setOwner(final AbstractParty value) {
         owner = value;
     }
 
@@ -477,12 +486,17 @@
      * the reference system is always null. The reference system is defined when the location types are
      * given to the {@link ReferencingByIdentifiers} constructor for example.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return {@code null}.
      *
      * @see ReferencingByIdentifiers#getLocationTypes()
      */
     @Override
-    public ReferenceSystemUsingIdentifiers getReferenceSystem() {
+    public ReferencingByIdentifiers getReferenceSystem() {
         return null;
     }
 }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationTypeAdapter.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationTypeAdapter.java
new file mode 100644
index 0000000..2950e77
--- /dev/null
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationTypeAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * 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.referencing.gazetteer;
+
+import java.util.List;
+import java.util.Map;
+import java.util.IdentityHashMap;
+import org.opengis.util.InternationalString;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
+
+
+/**
+ * Workaround for the lack of {@code LocationType} interface in GeoAPI 3.0.
+ * This workaround will be removed if a future GeoAPI version publish that interface,
+ * or if {@link AbstractLocationType} is made public.
+ */
+final class ModifiableLocationTypeAdapter extends ModifiableLocationType {
+    /**
+     * The reference system of the original type.
+     * This is the only information not stored in {@link ModifiableLocationType}.
+     */
+    private final ReferencingByIdentifiers referenceSystem;
+
+    /**
+     * Copies all information from the given type.
+     */
+    private ModifiableLocationTypeAdapter(final AbstractLocationType type,
+            final Map<AbstractLocationType,ModifiableLocationTypeAdapter> previous)
+    {
+        super(type.getName());
+        setTheme(type.getTheme());
+        setDefinition(type.getDefinition());
+        setTerritoryOfUse(type.getTerritoryOfUse());
+        setOwner(type.getOwner());
+        for (final InternationalString s : type.getIdentifications()) {
+            addIdentification(s);
+        }
+        referenceSystem = type.getReferenceSystem();
+        for (final AbstractLocationType c : type.getChildren()) {
+            ModifiableLocationTypeAdapter p = previous.get(c);
+            if (p == null) {
+                p = new ModifiableLocationTypeAdapter(c, previous);
+                previous.put(c, p);
+            }
+            p.addParent(this);
+        }
+    }
+
+    /**
+     * Returns type type as-is if it is already an instance of {@code ModifiableLocationType},
+     * or returns a copy otherwise.
+     */
+    static ModifiableLocationType copy(final AbstractLocationType type) {
+        if (type instanceof ModifiableLocationType) {
+            return (ModifiableLocationType) type;
+        } else {
+            return new ModifiableLocationTypeAdapter(type,
+                    new IdentityHashMap<AbstractLocationType,ModifiableLocationTypeAdapter>());
+        }
+    }
+
+    /**
+     * Copies a list of location types.
+     */
+    static List<ModifiableLocationType> copy(final List<? extends AbstractLocationType> types) {
+        final Map<AbstractLocationType,ModifiableLocationTypeAdapter> previous = new IdentityHashMap<>();
+        final ModifiableLocationType[] nt = new ModifiableLocationType[types.size()];
+        for (int i=0; i<nt.length; i++) {
+            final AbstractLocationType c = types.get(i);
+            ModifiableLocationTypeAdapter p = previous.get(c);
+            if (p == null) {
+                p = new ModifiableLocationTypeAdapter(c, previous);
+                previous.put(c, p);
+            }
+            nt[i] = p;
+        }
+        return UnmodifiableArrayList.wrap(nt);
+    }
+
+    /**
+     * Returns the reference system of the type given to the constructor.
+     */
+    @Override
+    public ReferencingByIdentifiers getReferenceSystem() {
+        return referenceSystem;
+    }
+}
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
index 45477c9..3ca0660 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
@@ -46,10 +46,7 @@
 import org.apache.sis.util.resources.Vocabulary;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -72,7 +69,25 @@
  * @module
  */
 @XmlTransient
-public abstract class ReferencingByIdentifiers extends AbstractReferenceSystem implements ReferenceSystemUsingIdentifiers {
+public abstract class ReferencingByIdentifiers extends AbstractReferenceSystem {
+    /**
+     * Key for the <code>{@value}</code> property to be given to the
+     * object factory {@code createFoo(…)} methods.
+     * This is used for setting the value to be returned by {@link #getTheme()}.
+     *
+     * @see #getTheme()
+     */
+    public static final String THEME_KEY = "theme";
+
+    /**
+     * Key for the <code>{@value}</code> property to be given to the
+     * object factory {@code createFoo(…)} methods.
+     * This is used for setting the value to be returned by {@link #getOverallOwner()}.
+     *
+     * @see #getOverallOwner()
+     */
+    public static final String OVERALL_OWNER_KEY = "overallOwner";
+
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -90,7 +105,7 @@
      *
      * @see #getOverallOwner()
      */
-    private final Party overallOwner;
+    private final AbstractParty overallOwner;
 
     /**
      * Description of location type(s) in the spatial reference system.
@@ -98,7 +113,7 @@
      *
      * @see #getLocationTypes()
      */
-    private final List<LocationType> locationTypes;
+    final List<AbstractLocationType> locationTypes;
 
     /**
      * Creates a reference system from the given properties.
@@ -114,13 +129,13 @@
      *     <th>Returned by</th>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers#THEME_KEY}</td>
+     *     <td>"theme"</td>
      *     <td>{@link String} or {@link InternationalString}</td>
      *     <td>{@link #getTheme()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers#OVERALL_OWNER_KEY}</td>
-     *     <td>{@link Party}</td>
+     *     <td>"overallOwner"</td>
+     *     <td>{@code Party}</td>
      *     <td>{@link #getOverallOwner()}</td>
      *   </tr>
      *   <tr>
@@ -158,18 +173,23 @@
      *   </tr>
      * </table>
      *
-     * This constructor copies the given {@link LocationType} instances as per
-     * {@link ModifiableLocationType#snapshot(ReferenceSystemUsingIdentifiers, LocationType...)}.
+     * This constructor copies the given {@code LocationType} instances as per
+     * {@code ModifiableLocationType.snapshot(ReferenceSystemUsingIdentifiers, LocationType...)}.
      * Changes in the given location types after construction will not affect this {@code ReferencingByIdentifiers}.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * In a future SIS version, the type of array elements may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.LocationType} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param properties  the properties to be given to the reference system.
      * @param types       description of location type(s) in the spatial reference system.
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
-    public ReferencingByIdentifiers(final Map<String,?> properties, final LocationType... types) {
+    public ReferencingByIdentifiers(final Map<String,?> properties, final ModifiableLocationType... types) {
         super(properties);
         theme = Types.toInternationalString(properties, THEME_KEY);
-        overallOwner = Containers.property(properties, OVERALL_OWNER_KEY, Party.class);
+        overallOwner = Containers.property(properties, OVERALL_OWNER_KEY, AbstractParty.class);
         /*
          * Having the 'this' reference escaped in object construction should not be an issue here because
          * we invoke package-private method in such a way that if an exception is thrown, the whole tree
@@ -186,7 +206,7 @@
      * @param id     an identifier for the reference system. Use SIS namespace until we find an authority for them.
      * @param party  the overall owner, or {@code null} if none.
      */
-    static Map<String,Object> properties(final Object name, final String id, final Party party) {
+    static Map<String,Object> properties(final Object name, final String id, final AbstractParty party) {
         final Map<String,Object> properties = new HashMap<>(8);
         properties.put(NAME_KEY, name);
         properties.put(IDENTIFIERS_KEY, new ImmutableIdentifier(Citations.SIS, Constants.SIS, id));
@@ -195,18 +215,6 @@
         properties.put(OVERALL_OWNER_KEY, party);
         return properties;
     }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The default implementation returns {@code ReferenceSystemUsingIdentifiers.class}.
-     *
-     * @return the GeoAPI interface implemented by this class.
-     */
-    @Override
-    public Class<? extends ReferenceSystemUsingIdentifiers> getInterface() {
-        return ReferenceSystemUsingIdentifiers.class;
-    }
-
     /**
      * Property used to characterize the spatial reference system.
      *
@@ -214,7 +222,6 @@
      *
      * @see ModifiableLocationType#getTheme()
      */
-    @Override
     public InternationalString getTheme() {
         return theme;
     }
@@ -222,13 +229,17 @@
     /**
      * Authority with overall responsibility for the spatial reference system.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @return authority with overall responsibility for the spatial reference system.
      *
      * @see ModifiableLocationType#getOwner()
      * @see AbstractLocation#getAdministrator()
      */
-    @Override
-    public Party getOverallOwner() {
+    public AbstractParty getOverallOwner() {
         return overallOwner;
     }
 
@@ -236,20 +247,24 @@
      * Description of location type(s) in the spatial reference system.
      * The collection returned by this method is unmodifiable.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of elements type may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return description of location type(s) in the spatial reference system.
      *
      * @see ModifiableLocationType#getReferenceSystem()
      */
-    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Because the collection is unmodifiable.
-    public List<? extends LocationType> getLocationTypes() {
-        return locationTypes;
+    public List<? extends ModifiableLocationType> getLocationTypes() {
+        return ModifiableLocationTypeAdapter.copy(locationTypes);
     }
 
     /**
      * Returns the first location type.
      */
-    final LocationType rootType() {
+    final AbstractLocationType rootType() {
         return locationTypes.get(0);
     }
 
@@ -353,11 +368,16 @@
          * Decodes the given identifier into a latitude and a longitude.
          * The axis order depends on the coordinate reference system of the enclosing {@link ReferencingByIdentifiers}.
          *
+         * <div class="warning"><b>Upcoming API change — generalization</b><br>
+         * in a future SIS version, the type of returned element may be generalized
+         * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+         * This change is pending GeoAPI revision.</div>
+         *
          * @param  identifier  identifier string to decode.
          * @return a new geographic coordinate for the given identifier.
          * @throws TransformException if an error occurred while parsing the given string.
          */
-        public abstract Location decode(CharSequence identifier) throws TransformException;
+        public abstract AbstractLocation decode(CharSequence identifier) throws TransformException;
 
         /**
          * Logs a warning for a recoverable error while transforming a position. This is used for implementations
@@ -399,7 +419,7 @@
                        locationTypes.equals(that.locationTypes);
             }
             case BY_CONTRACT: {
-                final ReferenceSystemUsingIdentifiers that = (ReferenceSystemUsingIdentifiers) object;
+                final ReferencingByIdentifiers that = (ReferencingByIdentifiers) object;
                 if (!Utilities.deepEquals(getTheme(),        that.getTheme(),        mode) ||
                     !Utilities.deepEquals(getOverallOwner(), that.getOverallOwner(), mode))
                 {
@@ -409,8 +429,8 @@
             }
             default: {
                 // Theme and owner are metadata, so they can be ignored.
-                final ReferenceSystemUsingIdentifiers that = (ReferenceSystemUsingIdentifiers) object;
-                return Utilities.deepEquals(getLocationTypes(), that.getLocationTypes(), mode);
+                final ReferencingByIdentifiers that = (ReferencingByIdentifiers) object;
+                return Utilities.deepEquals(locationTypes, that.locationTypes, mode);
             }
         }
     }
@@ -446,7 +466,7 @@
             formatter.newLine();
             formatter.append(new SubElement("Owner", overallOwner.getName()));
         }
-        for (final LocationType type : locationTypes) {
+        for (final AbstractLocationType type : locationTypes) {
             formatter.newLine();
             formatter.append(new SubElement("LocationType", type.getName()));
         }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/SimpleLocation.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/SimpleLocation.java
index 7a98cbd..a5d6270 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/SimpleLocation.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/SimpleLocation.java
@@ -21,7 +21,6 @@
 import org.opengis.geometry.coordinate.Position;
 import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.referencing.gazetteer.LocationType;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -91,7 +90,7 @@
      * @param type        the description of the nature of this geographic identifier.
      * @param identifier  the geographic identifier to be returned by {@link #getGeographicIdentifier()}.
      */
-    SimpleLocation(final LocationType type, final CharSequence identifier) {
+    SimpleLocation(final AbstractLocationType type, final CharSequence identifier) {
         super(type, identifier);
     }
 
@@ -339,7 +338,7 @@
          * @param type        the description of the nature of this geographic identifier.
          * @param identifier  the geographic identifier to be returned by {@link #getGeographicIdentifier()}.
          */
-        Projected(final LocationType type, final CharSequence identifier) {
+        Projected(final AbstractLocationType type, final CharSequence identifier) {
             super(type, identifier);
         }
 
diff --git a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java
index 4af4c36..bda9005 100644
--- a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java
+++ b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java
@@ -34,10 +34,6 @@
 
 import static org.junit.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Tests methods from the {@link GeohashReferenceSystem} class.
@@ -254,7 +250,7 @@
      */
     private void testDecode(final GeohashReferenceSystem.Coder coder, final int λi, final int φi) throws TransformException {
         for (final Place place : PLACES) {
-            final Location location = coder.decode(place.geohash);
+            final AbstractLocation location = coder.decode(place.geohash);
             final DirectPosition result = location.getPosition().getDirectPosition();
             assertEquals(place.name, place.longitude, result.getOrdinate(λi), TOLERANCE);
             assertEquals(place.name, place.latitude,  result.getOrdinate(φi), TOLERANCE);
@@ -272,7 +268,7 @@
         assertEquals("theme", "Mapping",      rs.getTheme().toString(Locale.ENGLISH));
         assertEquals("theme", "Cartographie", rs.getTheme().toString(Locale.FRENCH));
 
-        final LocationType type = TestUtilities.getSingleton(rs.getLocationTypes());
+        final AbstractLocationType type = TestUtilities.getSingleton(rs.getLocationTypes());
         assertEquals("type", "Geohash", type.getName().toString(Locale.ENGLISH));
         assertEquals("parent",   0, type.getParents().size());
         assertEquals("children", 0, type.getChildren().size());
diff --git a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
index 34a3971..e69e23a 100644
--- a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
+++ b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
@@ -25,9 +25,6 @@
 
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Tests {@link AbstractLocationType}, {@link FinalLocationType} and {@link ModifiableLocationType}.
@@ -115,7 +112,7 @@
     /**
      * Verifies the value of a "administrative area" location type.
      */
-    private static void verify(final LocationType[] type) {
+    private static void verify(final AbstractLocationType[] type) {
         assertEquals(5, type.length);
         verify(type[0], "administrative area",
                         "local administration",
@@ -147,8 +144,8 @@
     /**
      * Verifies the value of a location type created by or copied from {@link #create(boolean)}.
      */
-    private static void verify(final LocationType type, final String name, final String theme, final String definition,
-            final String identification, final String owner)
+    private static void verify(final AbstractLocationType type, final String name, final String theme,
+            final String definition, final String identification, final String owner)
     {
         assertEquals("name",           name,           String.valueOf(type.getName()));
         assertEquals("theme",          theme,          String.valueOf(type.getTheme()));
@@ -183,8 +180,8 @@
     @Test
     @DependsOnMethod("testInheritance")
     public void testSnapshot() {
-        final List<LocationType> snapshot = ModifiableLocationType.snapshot(null, create(true));
-        verify(snapshot.toArray(new LocationType[snapshot.size()]));
+        final List<AbstractLocationType> snapshot = ModifiableLocationType.snapshot(null, create(true));
+        verify(snapshot.toArray(new AbstractLocationType[snapshot.size()]));
     }
 
     /**
diff --git a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
index c07ed00..3f030ad 100644
--- a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
+++ b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
@@ -41,12 +41,9 @@
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.util.Debug;
 
-// Branch-dependent imports
-import org.opengis.referencing.gazetteer.Location;
-
 
 /**
- * A Swing panel drawing {@link Location} instances.
+ * A Swing panel drawing {@code Location} instances.
  * This is used for debugging purpose only.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -215,7 +212,7 @@
      * @throws FactoryException if a transformation to the display CRS can not be obtained.
      * @throws TransformException if an error occurred while transforming an envelope.
      */
-    public void addLocation(final String label, final Location location) throws FactoryException, TransformException {
+    public void addLocation(final String label, final AbstractLocation location) throws FactoryException, TransformException {
         final Envelope envelope = location.getEnvelope();
         final MathTransform2D tr = (MathTransform2D) CRS.findOperation(
                 envelope.getCoordinateReferenceSystem(), displayCRS, null).getMathTransform();
diff --git a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
index 2a76f68..9964b4c 100644
--- a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
+++ b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
@@ -47,10 +47,6 @@
 import static org.apache.sis.measure.Units.ARC_MINUTE;
 import static org.junit.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Tests {@link MilitaryGridReferenceSystem}.
@@ -71,15 +67,15 @@
         assertEquals("theme", "Mapping",      rs.getTheme().toString(Locale.ENGLISH));
         assertEquals("theme", "Cartographie", rs.getTheme().toString(Locale.FRENCH));
 
-        final LocationType gzd = TestUtilities.getSingleton(rs.getLocationTypes());
+        final AbstractLocationType gzd = TestUtilities.getSingleton(rs.getLocationTypes());
         assertEquals("type", "Grid zone designator", gzd.getName().toString(Locale.ENGLISH));
         assertEquals("parent", 0, gzd.getParents().size());
 
-        final LocationType sid = TestUtilities.getSingleton(gzd.getChildren());
+        final AbstractLocationType sid = TestUtilities.getSingleton(gzd.getChildren());
         assertEquals("type", "100 km square identifier", sid.getName().toString(Locale.ENGLISH));
         assertSame("parent", gzd, TestUtilities.getSingleton(sid.getParents()));
 
-        final LocationType gc = TestUtilities.getSingleton(sid.getChildren());
+        final AbstractLocationType gc = TestUtilities.getSingleton(sid.getChildren());
         assertEquals("type", "Grid coordinate", gc.getName().toString(Locale.ENGLISH));
         assertSame("parent", sid, TestUtilities.getSingleton(gc.getParents()));
     }
@@ -217,7 +213,7 @@
     private static DirectPosition decode(final MilitaryGridReferenceSystem.Coder coder, final String reference)
             throws TransformException
     {
-        final Location loc = coder.decode(reference);
+        final AbstractLocation loc = coder.decode(reference);
         final Envelope2D envelope = new Envelope2D(loc.getEnvelope());
         final DirectPosition2D pos = new DirectPosition2D(loc.getPosition().getDirectPosition());
         assertTrue(reference, envelope.contains(pos));
diff --git a/core/sis-referencing/pom.xml b/core/sis-referencing/pom.xml
index 8fbaa28..abc463a 100644
--- a/core/sis-referencing/pom.xml
+++ b/core/sis-referencing/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
   <groupId>org.apache.sis.core</groupId>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
index e310d99..a7b24f5 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
@@ -25,7 +25,6 @@
 import java.util.Objects;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CoordinateSystem;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
index 6263e26..09bb5b4 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
@@ -31,7 +31,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CoordinateSystem;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/ArrayEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/ArrayEnvelope.java
index 6ea6b05..aa6bb04 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/ArrayEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/ArrayEnvelope.java
@@ -27,7 +27,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.cs.RangeMeaning;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
index a4339ef..1d8f78b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
@@ -21,7 +21,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
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 1fbacc1..66e069c 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
@@ -33,7 +33,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.apache.sis.internal.referencing.TemporalAccessor;
 import org.apache.sis.internal.referencing.AxisDirections;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/ImmutableEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/ImmutableEnvelope.java
index 0124952..e674408 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/ImmutableEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/ImmutableEnvelope.java
@@ -25,7 +25,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/MismatchedReferenceSystemException.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/MismatchedReferenceSystemException.java
new file mode 100644
index 0000000..ce27ce0
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/MismatchedReferenceSystemException.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+
+/**
+ * Indicates that an object cannot be constructed because of a mismatch in the
+ * {@linkplain org.opengis.referencing.ReferenceSystem reference systems} of
+ * geometric components.
+ *
+ * @author  Martin Desruisseaux (IRD)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public class MismatchedReferenceSystemException extends IllegalArgumentException {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 6222334569692693273L;
+
+    /**
+     * Creates an exception with no message.
+     */
+    public MismatchedReferenceSystemException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message The detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     */
+    public MismatchedReferenceSystemException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Creates an exception with the specified message and cause.
+     *
+     * @param  message The detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     * @param  cause The cause.
+     */
+    public MismatchedReferenceSystemException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/UnmodifiableGeometryException.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/UnmodifiableGeometryException.java
new file mode 100644
index 0000000..3285613
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/UnmodifiableGeometryException.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+
+/**
+ * Indicates that an operation is not allowed on a geometry object
+ * because it is unmodifiable. Note that unmodifiable geometries are not necessarily immutable;
+ * they are just not allowed to be modified through the {@code setFoo(...)} method that
+ * raised this exception. Whatever an unmodifiable geometry is immutable or not is
+ * implementation dependent.
+ *
+ * @author  Martin Desruisseaux (IRD)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public class UnmodifiableGeometryException extends UnsupportedOperationException {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 8679047625299612669L;
+
+    /**
+     * Creates an exception with no message.
+     */
+    public UnmodifiableGeometryException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message The detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     */
+    public UnmodifiableGeometryException(final String message) {
+        super(message);
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundAdjustment.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundAdjustment.java
index a5578e2..bb4fe3c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundAdjustment.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundAdjustment.java
@@ -197,7 +197,7 @@
         try {
             return CRS.findOperation(source, target, geographicDomain);
         } catch (FactoryException e) {
-            throw new TransformException(e);
+            throw new TransformException(e.getMessage(), e);
         }
     }
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java
index 0de9cc7..fec9139 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java
@@ -261,7 +261,7 @@
          */
         final Map<String,Object> merged = new HashMap<>(expected);
         merged.putAll(actual);  // May overwrite pre-defined properties.
-        mergeArrays(GeneralParameterDescriptor.ALIAS_KEY,       GenericName.class, provided.getAlias(),       merged, complete.getName());
+        mergeArrays(GeneralParameterDescriptor.ALIAS_KEY,       GenericName.class, provided.getAlias(), merged, complete.getName());
         mergeArrays(GeneralParameterDescriptor.IDENTIFIERS_KEY, ReferenceIdentifier.class, provided.getIdentifiers(), merged, null);
         if (isGroup) {
             final List<GeneralParameterDescriptor> descriptors = ((ParameterDescriptorGroup) provided).descriptors();
@@ -455,8 +455,8 @@
     private static NamedIdentifier toNamedIdentifier(final Object name) {
         if (name == null || name.getClass() == NamedIdentifier.class) {
             return (NamedIdentifier) name;
-        } else if (name instanceof Identifier) {
-            return new NamedIdentifier((Identifier) name);
+        } else if (name instanceof ReferenceIdentifier) {
+            return new NamedIdentifier((ReferenceIdentifier) name);
         } else {
             return new NamedIdentifier((GenericName) name);
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CD_ParametricDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CD_ParametricDatum.java
index 408b95f..ba34080 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CD_ParametricDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CD_ParametricDatum.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.referencing;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.referencing.datum.ParametricDatum;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 import org.apache.sis.referencing.datum.DefaultParametricDatum;
 
@@ -32,7 +31,7 @@
  * @since   0.7
  * @module
  */
-public final class CD_ParametricDatum extends PropertyType<CD_ParametricDatum, ParametricDatum> {
+public final class CD_ParametricDatum extends PropertyType<CD_ParametricDatum, DefaultParametricDatum> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -47,14 +46,14 @@
      * @return {@code ParametricDatum.class}
      */
     @Override
-    protected Class<ParametricDatum> getBoundType() {
-        return ParametricDatum.class;
+    protected Class<DefaultParametricDatum> getBoundType() {
+        return DefaultParametricDatum.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CD_ParametricDatum(final ParametricDatum datum) {
+    private CD_ParametricDatum(final DefaultParametricDatum datum) {
         super(datum);
     }
 
@@ -66,7 +65,7 @@
      * @return a {@code PropertyType} wrapping the given the element.
      */
     @Override
-    protected CD_ParametricDatum wrap(final ParametricDatum datum) {
+    protected CD_ParametricDatum wrap(final DefaultParametricDatum datum) {
         return new CD_ParametricDatum(datum);
     }
 
@@ -79,7 +78,7 @@
      */
     @XmlElement(name = "ParametricDatum")
     public DefaultParametricDatum getElement() {
-        return DefaultParametricDatum.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CS_ParametricCS.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CS_ParametricCS.java
index 7cb4899..d361200 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CS_ParametricCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CS_ParametricCS.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.referencing;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.referencing.cs.ParametricCS;
 import org.apache.sis.referencing.cs.DefaultParametricCS;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.7
  * @module
  */
-public final class CS_ParametricCS extends PropertyType<CS_ParametricCS, ParametricCS> {
+public final class CS_ParametricCS extends PropertyType<CS_ParametricCS, DefaultParametricCS> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -46,14 +45,14 @@
      * @return {@code ParametricCS.class}
      */
     @Override
-    protected Class<ParametricCS> getBoundType() {
-        return ParametricCS.class;
+    protected Class<DefaultParametricCS> getBoundType() {
+        return DefaultParametricCS.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CS_ParametricCS(final ParametricCS cs) {
+    private CS_ParametricCS(final DefaultParametricCS cs) {
         super(cs);
     }
 
@@ -65,7 +64,7 @@
      * @return a {@code PropertyType} wrapping the given the element.
      */
     @Override
-    protected CS_ParametricCS wrap(final ParametricCS cs) {
+    protected CS_ParametricCS wrap(final DefaultParametricCS cs) {
         return new CS_ParametricCS(cs);
     }
 
@@ -78,7 +77,7 @@
      */
     @XmlElement(name = "ParametricCS")
     public DefaultParametricCS getElement() {
-        return DefaultParametricCS.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java
index 3c2eedb..f37538e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java
@@ -76,7 +76,7 @@
      *
      * @param identifier  the identifier from which to get the values.
      */
-    Code(final Identifier identifier) {
+    Code(final ReferenceIdentifier identifier) {
         code      = identifier.getCode();
         codeSpace = identifier.getCodeSpace();
         String version = identifier.getVersion();
@@ -165,12 +165,12 @@
      * @param  identifiers  the object identifiers, or {@code null} if none.
      * @return the {@code <gml:identifier>} as a {@code Code} instance, or {@code null} if none.
      */
-    public static Code forIdentifiedObject(final Class<?> type, final Iterable<? extends Identifier> identifiers) {
+    public static Code forIdentifiedObject(final Class<?> type, final Iterable<? extends ReferenceIdentifier> identifiers) {
         if (identifiers != null) {
             boolean isHTTP = false;
             boolean isEPSG = false;
-            Identifier fallback = null;
-            for (final Identifier identifier : identifiers) {
+            ReferenceIdentifier fallback = null;
+            for (final ReferenceIdentifier identifier : identifiers) {
                 final String code = identifier.getCode();
                 if (code == null) continue;                                                 // Paranoiac check.
                 if (code.regionMatches(true, 0, "urn:", 0, 4)) {
@@ -232,10 +232,12 @@
                             if (authority != null) {
                                 for (final Identifier id : authority.getIdentifiers()) {
                                     if (Constants.EPSG.equalsIgnoreCase(id.getCode())) {
-                                        final String cs = id.getCodeSpace();
-                                        if (cs != null) {
-                                            code.codeSpace = cs;
-                                            break;
+                                        if (id instanceof ReferenceIdentifier) {
+                                            final String cs = ((ReferenceIdentifier) id).getCodeSpace();
+                                            if (cs != null) {
+                                                code.codeSpace = cs;
+                                                break;
+                                            }
                                         }
                                     }
                                 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/RS_Identifier.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/RS_Identifier.java
index d8bf813..75fc88e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/RS_Identifier.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/RS_Identifier.java
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.jaxb.referencing;
 
 import javax.xml.bind.annotation.adapters.XmlAdapter;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.metadata.Identifier;
 
 
@@ -36,7 +37,7 @@
  *   <gml:identifier codeSpace="EPSG">4326</gml:identifier>
  * }
  *
- * If the {@code Identifier} to marshal contains a {@linkplain Identifier#getVersion() version},
+ * If the {@code Identifier} to marshal contains a {@linkplain ReferenceIdentifier#getVersion() version},
  * then this adapter concatenates the version to the codespace in a "URI-like" way like below:
  *
  * {@preformat xml
@@ -61,7 +62,7 @@
  * @since   0.4
  * @module
  */
-public final class RS_Identifier extends XmlAdapter<Code, Identifier> {
+public final class RS_Identifier extends XmlAdapter<Code, ReferenceIdentifier> {
     /**
      * Substitutes the wrapper value read from an XML stream by the object which will
      * represents the identifier. JAXB calls automatically this method at unmarshalling time.
@@ -70,7 +71,7 @@
      * @return an identifier which represents the value.
      */
     @Override
-    public Identifier unmarshal(final Code value) {
+    public ReferenceIdentifier unmarshal(final Code value) {
         return (value != null) ? value.getIdentifier() : null;
     }
 
@@ -82,7 +83,7 @@
      * @return the adapter for the given metadata.
      */
     @Override
-    public Code marshal(final Identifier value) {
+    public Code marshal(final ReferenceIdentifier value) {
         return (value != null) ? new Code(value) : null;
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
index eeba6ee..32d5e1c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
@@ -36,7 +36,7 @@
 
 import static org.opengis.referencing.cs.AxisDirection.*;
 import static org.opengis.annotation.Obligation.CONDITIONAL;
-import static org.opengis.annotation.Specification.ISO_19162;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 import static org.apache.sis.util.CharSequences.*;
 
 
@@ -81,7 +81,7 @@
      *
      * @since 0.7
      */
-    @UML(identifier="awayFrom", obligation=CONDITIONAL, specification=ISO_19162)
+    @UML(identifier="awayFrom", obligation=CONDITIONAL, specification=UNSPECIFIED)
     public static final AxisDirection AWAY_FROM = AxisDirection.valueOf("AWAY_FROM");
 
     /**
@@ -90,7 +90,7 @@
      *
      * @since 0.7
      */
-    @UML(identifier="clockwise", obligation=CONDITIONAL, specification=ISO_19162)
+    @UML(identifier="clockwise", obligation=CONDITIONAL, specification=UNSPECIFIED)
     public static final AxisDirection CLOCKWISE = AxisDirection.valueOf("CLOCKWISE");
 
     /**
@@ -99,7 +99,7 @@
      *
      * @since 0.7
      */
-    @UML(identifier="counterClockwise", obligation=CONDITIONAL, specification=ISO_19162)
+    @UML(identifier="counterClockwise", obligation=CONDITIONAL, specification=UNSPECIFIED)
     public static final AxisDirection COUNTER_CLOCKWISE = AxisDirection.valueOf("COUNTER_CLOCKWISE");
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyCRS.java
index 265dee9..4af5e0e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyCRS.java
@@ -24,7 +24,6 @@
 import org.opengis.referencing.crs.GeocentricCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ImageCRS;
-import org.opengis.referencing.crs.ParametricCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.VerticalCRS;
@@ -101,9 +100,4 @@
     public VerticalCRS createVerticalCRS(String code) throws FactoryException {
         return factory().createVerticalCRS(code);
     }
-
-    @Override
-    public ParametricCRS createParametricCRS(String code) throws FactoryException {
-        return factory().createParametricCRS(code);
-    }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyCS.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyCS.java
index 889ef4f..27685d8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyCS.java
@@ -24,7 +24,6 @@
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CylindricalCS;
 import org.opengis.referencing.cs.EllipsoidalCS;
-import org.opengis.referencing.cs.ParametricCS;
 import org.opengis.referencing.cs.PolarCS;
 import org.opengis.referencing.cs.SphericalCS;
 import org.opengis.referencing.cs.TimeCS;
@@ -91,11 +90,6 @@
     }
 
     @Override
-    public ParametricCS createParametricCS(String code) throws FactoryException {
-        return factory().createParametricCS(code);
-    }
-
-    @Override
     public CoordinateSystemAxis createCoordinateSystemAxis(String code) throws FactoryException {
         return factory().createCoordinateSystemAxis(code);
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyDatum.java
index 3d553b6..492e849 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxyDatum.java
@@ -23,7 +23,6 @@
 import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.datum.EngineeringDatum;
 import org.opengis.referencing.datum.ImageDatum;
-import org.opengis.referencing.datum.ParametricDatum;
 import org.opengis.referencing.datum.TemporalDatum;
 import org.opengis.referencing.datum.VerticalDatum;
 import org.opengis.util.FactoryException;
@@ -87,9 +86,4 @@
     public VerticalDatum createVerticalDatum(String code) throws FactoryException {
         return factory().createVerticalDatum(code);
     }
-
-    @Override
-    public ParametricDatum createParametricDatum(String code) throws FactoryException {
-        return factory().createParametricDatum(code);
-    }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/NilReferencingObject.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/NilReferencingObject.java
index cac8734..46b6c40 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/NilReferencingObject.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/NilReferencingObject.java
@@ -16,11 +16,16 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.Set;
+import java.util.Collection;
+import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.metadata.extent.Extent;
 import org.apache.sis.xml.NilReason;
 import org.apache.sis.xml.NilObject;
+import org.apache.sis.io.wkt.UnformattableObjectException;
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.util.resources.Vocabulary;
 
@@ -75,6 +80,21 @@
      * Returning null for collection are okay in the particular case of SIS implementation,
      * because the constructor will replace empty collections by null references anyway.
      */
-    @Override public ReferenceIdentifier getName()  {return UNNAMED;}
-    @Override public InternationalString getScope() {return null;}
+    @Override public ReferenceIdentifier      getName()        {return UNNAMED;}
+    @Override public Collection<GenericName>  getAlias()       {return null;}
+    @Override public Set<ReferenceIdentifier> getIdentifiers() {return null;}
+    @Override public InternationalString      getRemarks()     {return null;}
+    @Override public InternationalString      getScope()       {return null;}
+    @Override public Extent getDomainOfValidity()              {return null;}
+
+    /**
+     * Throws the exception in all cases.
+     *
+     * @return never return.
+     * @throws UnformattableObjectException always thrown.
+     */
+    @Override
+    public String toWKT() throws UnformattableObjectException {
+        throw new UnformattableObjectException();
+    }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/RTreeNode.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/RTreeNode.java
index 0c28dcb..f93219d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/RTreeNode.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/RTreeNode.java
@@ -21,7 +21,6 @@
 import java.util.function.Consumer;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.io.wkt.Formatter;
@@ -30,6 +29,7 @@
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.DefaultTreeTable;
+import org.apache.sis.geometry.MismatchedReferenceSystemException;
 
 
 /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java
index 78560bc..c27fd14 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java
@@ -35,6 +35,9 @@
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.util.resources.Errors;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+
 
 /**
  * A container of factories frequently used together.
@@ -104,7 +107,7 @@
      * Factory for fetching operation methods and creating defining conversions.
      * This is needed only for user-defined projected coordinate reference system.
      */
-    private CoordinateOperationFactory operationFactory;
+    private DefaultCoordinateOperationFactory operationFactory;
 
     /**
      * The {@linkplain org.opengis.referencing.operation.MathTransform math transform} factory.
@@ -146,7 +149,7 @@
         this.crsFactory        = crsFactory;
         this.csFactory         = csFactory;
         this.datumFactory      = datumFactory;
-        this.operationFactory  = operationFactory;
+        this.operationFactory  = (DefaultCoordinateOperationFactory) operationFactory;      // Because of GeoAPI 3.0 limitation.
         this.mtFactory         = mtFactory;
     }
 
@@ -175,7 +178,7 @@
         if (type == CRSFactory.class)                 return crsFactory       != (crsFactory       = (CRSFactory)                 factory);
         if (type == CSFactory.class)                  return csFactory        != (csFactory        = (CSFactory)                  factory);
         if (type == DatumFactory.class)               return datumFactory     != (datumFactory     = (DatumFactory)               factory);
-        if (type == CoordinateOperationFactory.class) return operationFactory != (operationFactory = (CoordinateOperationFactory) factory);
+        if (type == CoordinateOperationFactory.class) return operationFactory != (operationFactory = (DefaultCoordinateOperationFactory) factory);
         if (type == MathTransformFactory.class)       return mtFactory        != (mtFactory        = (MathTransformFactory)       factory);
         throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "type", type));
     }
@@ -315,10 +318,15 @@
      *
      * @return the Coordinate Operation factory (never {@code null}).
      */
-    public final CoordinateOperationFactory getCoordinateOperationFactory() {
+    public final DefaultCoordinateOperationFactory getCoordinateOperationFactory() {
         if (operationFactory == null) {
-            operationFactory = CoordinateOperations.getCoordinateOperationFactory(defaultProperties, mtFactory, crsFactory, csFactory);
+            CoordinateOperationFactory op = CoordinateOperations.getCoordinateOperationFactory(defaultProperties, mtFactory, crsFactory, csFactory);
             defaultProperties = null;       // Not needed anymore.
+            if (op instanceof DefaultCoordinateOperationFactory) {
+                operationFactory = (DefaultCoordinateOperationFactory) op;
+            } else {
+                operationFactory = CoordinateOperations.factory();
+            }
         }
         return operationFactory;
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
index c3825e8..aa8b68b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
@@ -487,7 +487,7 @@
         final UML uml = type.getAnnotation(UML.class);
         if (uml != null) {
             final Specification spec = uml.specification();
-            if (spec == Specification.ISO_19111 || spec == Specification.ISO_19111_2) {
+            if (spec == Specification.ISO_19111) {
                 final String name = uml.identifier();
                 final int length = name.length();
                 final StringBuilder buffer = new StringBuilder(length).append(name, name.indexOf('_') + 1, length);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
index 88bf198..13db4b9 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.Map;
 import java.util.Iterator;
 import java.util.Collection;
 import java.util.Locale;
@@ -24,17 +25,26 @@
 
 import org.opengis.util.FactoryException;
 import org.opengis.util.InternationalString;
+import org.opengis.util.NoSuchIdentifierException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.datum.DatumFactory;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.CRSFactory;
+import org.opengis.referencing.cs.CSFactory;
 import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.SingleOperation;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.CoordinateOperationFactory;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.OnLineFunction;
 import org.opengis.metadata.citation.OnlineResource;
@@ -48,23 +58,32 @@
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.CoordinateFormat;
+import org.apache.sis.internal.metadata.NameToIdentifier;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
+import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
 import org.apache.sis.parameter.DefaultParameterDescriptor;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultSpatialTemporalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.internal.metadata.ReferencingServices;
+import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.Deprecable;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Utilities;
 
@@ -441,7 +460,7 @@
          * If above code did not found an EPSG code, discard EPSG codes that
          * we may find in the loop below because they are probably invalid.
          */
-        for (final Identifier id : object.getIdentifiers()) {
+        for (final ReferenceIdentifier id : object.getIdentifiers()) {
             if (!Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
                 return IdentifiedObjects.toString(id);
             }
@@ -470,6 +489,86 @@
     }
 
     /**
+     * Creates a parametric CS. This method requires the SIS factory
+     * since parametric CRS were not available in GeoAPI 3.0.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  properties  the coordinate system name, and optionally other properties.
+     * @param  axis        the axis of the parametric coordinate system.
+     * @param  factory     the factory to use for creating the coordinate system.
+     * @return a parametric coordinate system using the given axes.
+     * @throws FactoryException if the parametric object creation failed.
+     *
+     * @since 0.7
+     */
+    public static CoordinateSystem createParametricCS(final Map<String,?> properties, final CoordinateSystemAxis axis,
+            CSFactory factory) throws FactoryException
+    {
+        if (!(factory instanceof GeodeticObjectFactory)) {
+            factory = DefaultFactories.forBuildin(CSFactory.class, GeodeticObjectFactory.class);
+        }
+        return ((GeodeticObjectFactory) factory).createParametricCS(properties, axis);
+    }
+
+    /**
+     * Creates a parametric datum. This method requires the SIS factory
+     * since parametric CRS were not available in GeoAPI 3.0.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  properties  the datum name, and optionally other properties.
+     * @param  factory     the factory to use for creating the datum.
+     * @return a parametric datum using the given name.
+     * @throws FactoryException if the parametric object creation failed.
+     *
+     * @since 0.7
+     */
+    public static Datum createParametricDatum(final Map<String,?> properties, DatumFactory factory)
+            throws FactoryException
+    {
+        if (!(factory instanceof GeodeticObjectFactory)) {
+            factory = DefaultFactories.forBuildin(DatumFactory.class, GeodeticObjectFactory.class);
+        }
+        return ((GeodeticObjectFactory) factory).createParametricDatum(properties);
+    }
+
+    /**
+     * Creates a parametric CRS. This method requires the SIS factory
+     * since parametric CRS were not available in GeoAPI 3.0.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  properties  the coordinate reference system name, and optionally other properties.
+     * @param  datum       the parametric datum.
+     * @param  cs          the parametric coordinate system.
+     * @param  factory     the factory to use for creating the coordinate reference system.
+     * @return a parametric coordinate system using the given axes.
+     * @throws FactoryException if the parametric object creation failed.
+     *
+     * @since 0.7
+     */
+    public static SingleCRS createParametricCRS(final Map<String,?> properties, final Datum datum,
+            final CoordinateSystem cs, CRSFactory factory) throws FactoryException
+    {
+        if (!(factory instanceof GeodeticObjectFactory)) {
+            factory = DefaultFactories.forBuildin(CRSFactory.class, GeodeticObjectFactory.class);
+        }
+        try {
+            return ((GeodeticObjectFactory) factory).createParametricCRS(properties,
+                    (DefaultParametricDatum) datum, (DefaultParametricCS) cs);
+        } catch (ClassCastException e) {
+            throw new InvalidGeodeticParameterException(e.toString(), e);
+        }
+    }
+
+    /**
      * Creates a format for {@link DirectPosition} instances.
      *
      * @param  locale    the locale for the new {@code Format}, or {@code null} for {@code Locale.ROOT}.
@@ -494,6 +593,70 @@
     }
 
     /**
+     * Returns the coordinate operation method for the given classification.
+     * This method checks if the given {@code opFactory} is a SIS implementation
+     * before to fallback on a slower fallback.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  opFactory  The coordinate operation factory to use if it is a SIS implementation.
+     * @param  mtFactory  The math transform factory to use as a fallback.
+     * @param  identifier The name or identifier of the operation method to search.
+     * @return The coordinate operation method for the given name or identifier.
+     * @throws FactoryException if an error occurred which searching for the given method.
+     *
+     * @since 0.6
+     */
+    public static OperationMethod getOperationMethod(final CoordinateOperationFactory opFactory,
+            final MathTransformFactory mtFactory, final String identifier) throws FactoryException
+    {
+        if (opFactory instanceof DefaultCoordinateOperationFactory) {
+            ((DefaultCoordinateOperationFactory) opFactory).getOperationMethod(identifier);
+        }
+        final OperationMethod method = getOperationMethod(mtFactory.getAvailableMethods(SingleOperation.class), identifier);
+        if (method != null) {
+            return method;
+        }
+        throw new NoSuchIdentifierException("No such operation method: " + identifier, identifier);
+    }
+
+    /**
+     * Returns the operation method for the specified name or identifier. The given argument shall be either a
+     * method name (e.g. <cite>"Transverse Mercator"</cite>) or one of its identifiers (e.g. {@code "EPSG:9807"}).
+     *
+     * @param  methods     the method candidates.
+     * @param  identifier  the name or identifier of the operation method to search.
+     * @return the coordinate operation method for the given name or identifier, or {@code null} if none.
+     *
+     * @see org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory#getOperationMethod(String)
+     * @see org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory#getOperationMethod(String)
+     *
+     * @since 0.6
+     */
+    private static OperationMethod getOperationMethod(final Iterable<? extends OperationMethod> methods, final String identifier) {
+        OperationMethod fallback = null;
+        for (final OperationMethod method : methods) {
+            if (IdentifiedObjects.isHeuristicMatchForName(method, identifier) ||
+                    NameToIdentifier.isHeuristicMatchForIdentifier(method.getIdentifiers(), identifier))
+            {
+                /*
+                 * Stop the iteration at the first non-deprecated method.
+                 * If we find only deprecated methods, take the first one.
+                 */
+                if (!(method instanceof Deprecable) || !((Deprecable) method).isDeprecated()) {
+                    return method;
+                }
+                if (fallback == null) {
+                    fallback = method;
+                }
+            }
+        }
+        return fallback;
+    }
+
+    /**
      * Returns information about the Apache SIS configuration.
      * See super-class for a list of keys.
      *
@@ -516,9 +679,9 @@
                     final String msg = Exceptions.getLocalizedMessage(e, locale);
                     return (msg != null) ? msg : e.toString();
                 }
-                if (authority != null) {
+                if (authority instanceof DefaultCitation) {
                     final OnLineFunction f = OnLineFunction.valueOf(CONNECTION);
-                    for (final OnlineResource res : authority.getOnlineResources()) {
+                    for (final OnlineResource res : ((DefaultCitation) authority).getOnlineResources()) {
                         if (f.equals(res.getFunction())) {
                             final InternationalString i18n = res.getDescription();
                             if (i18n != null) return i18n.toString(locale);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/VerticalDatumTypes.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/VerticalDatumTypes.java
index a6acaa0..d41bff1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/VerticalDatumTypes.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/VerticalDatumTypes.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.referencing;
 
 import java.util.Collection;
-import java.util.function.Predicate;
 import javax.measure.Unit;
 import org.opengis.util.CodeList;
 import org.opengis.util.GenericName;
@@ -35,15 +34,15 @@
  * Those constants are not in public API because they were intentionally omitted from ISO 19111,
  * and the ISO experts said that they should really not be public.
  *
- * <p>This class implements {@link Predicate} for opportunist reasons.
+ * <p>This class implements {@code CodeList.Filter} for opportunist reasons.
  * This implementation convenience may change in any future SIS version.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 0.7
  * @since   0.4
  * @module
  */
-public final class VerticalDatumTypes implements Predicate<CodeList<?>> {
+public final class VerticalDatumTypes implements CodeList.Filter {
     /**
      * A vertical datum for ellipsoidal heights that are measured along the
      * normal to the ellipsoid used in the definition of horizontal datum.
@@ -188,7 +187,7 @@
             for (int i=0; i<name.length();) {
                 final int c = name.codePointAt(i);
                 if (Character.isLetter(c)) {
-                    return CodeList.valueOf(VerticalDatumType.class, new VerticalDatumTypes(name), null);
+                    return CodeList.valueOf(VerticalDatumType.class, new VerticalDatumTypes(name));
                 }
                 i += Character.charCount(c);
             }
@@ -231,8 +230,19 @@
      * @return {@code true} if the code matches the criterion.
       */
     @Override
-    public boolean test(final CodeList<?> code) {
+    public boolean accept(final CodeList<?> code) {
         final int i = datum.indexOf(code.name());
         return (i == 0) || (i >= 0 && Character.isWhitespace(datum.codePointBefore(i)));
     }
+
+    /**
+     * Returns {@code null} for disabling the creation of new code list elements.
+     * This method is public as an implementation side-effect and should be ignored.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public String codename() {
+        return null;
+    }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
index 5a8e0ce..2ff349c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
@@ -21,12 +21,12 @@
 import javax.measure.Unit;
 import javax.measure.Quantity;
 import javax.measure.quantity.Angle;
-import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.GeneralParameterValue;
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
@@ -338,7 +338,7 @@
      */
     public static boolean isEPSG(final GeneralParameterDescriptor descriptor, final boolean ifUndefined) {
         if (descriptor != null) {
-            final Identifier id = descriptor.getName();
+            final ReferenceIdentifier id = descriptor.getName();
             if (id != null) {
                 final String cs = id.getCodeSpace();
                 if (cs != null) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/TileTranslation.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/TileTranslation.java
new file mode 100644
index 0000000..095ad78
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/TileTranslation.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.referencing.j2d;
+
+import java.awt.Dimension;
+import java.awt.geom.AffineTransform;
+
+
+/**
+ * An affine transform which is translated relative to an original transform.
+ * The translation terms are stored separately without modifying the transform.
+ * This class if for internal use by {@link TileOrganizer} only.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class TileTranslation {
+    /**
+     * The translated "grid to real world" transform, as an immutable instance.
+     */
+    private final AffineTransform gridToCRS;
+
+    /**
+     * The translation in "absolute units". This is the same units than for tiles at subsampling (1,1).
+     */
+    private final int dx, dy;
+
+    /**
+     * Creates a new translated transform. The translation is specified in "absolute units",
+     * i.e. in the same units than for tiles at subsampling (1,1).
+     *
+     * @param  subsampling  the {@linkplain Tile#getSubsampling() tile subsampling}.
+     * @param  reference    the "grid to real world" transform at subsampling (1,1).
+     * @param  dx           the translation along <var>x</var> axis in "absolute units".
+     * @param  dy           the translation along <var>y</var> axis in "absolute units".
+     */
+    TileTranslation(final Dimension subsampling, AffineTransform reference, int dx, int dy) {
+        this.dx = dx / subsampling.width;                           // It is okay to round toward zero.
+        this.dy = dy / subsampling.height;
+        dx %= subsampling.width;
+        dy %= subsampling.height;
+        reference = new AffineTransform(reference);
+        reference.scale(subsampling.width, subsampling.height);
+        reference.translate(dx, dy);                                // Correction for non-integer division of (dx,dy).
+        gridToCRS = new ImmutableAffineTransform(reference);
+    }
+
+    /**
+     * Applies the translation and the new "grid to CRS" transform on the given tile.
+     *
+     * @param  tile  the tile on which to apply the translation.
+     */
+    final void applyTo(final Tile tile) {
+        synchronized (tile) {
+            tile.translate(dx, dy);
+            tile.setGridToCRS(gridToCRS);
+        }
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
index 044b166..b8cc183 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
@@ -35,8 +35,8 @@
 import javax.measure.Unit;
 import javax.measure.Quantity;
 
+import org.opengis.util.CodeList;
 import org.opengis.util.InternationalString;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.extent.Extent;
@@ -46,6 +46,7 @@
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.parameter.GeneralParameterValue;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.crs.CompoundCRS;
@@ -852,17 +853,17 @@
             appendForSubtypes(object);
         }
         if (showIDs) {
-            Collection<? extends Identifier> identifiers = object.getIdentifiers();
+            Collection<ReferenceIdentifier> identifiers = object.getIdentifiers();
             if (identifiers != null) {                                                  // Paranoiac check
                 if (filterID) {
-                    for (final Identifier id : identifiers) {
+                    for (final ReferenceIdentifier id : identifiers) {
                         if (Citations.identifierMatches(authority, id.getAuthority())) {
                             identifiers = Collections.singleton(id);
                             break;
                         }
                     }
                 }
-                for (Identifier id : identifiers) {
+                for (ReferenceIdentifier id : identifiers) {
                     if (!(id instanceof FormattableObject)) {
                         id = ImmutableIdentifier.castOrCopy(id);
                     }
@@ -1147,7 +1148,7 @@
      *
      * @param  code  the code list to append to the WKT, or {@code null} if none.
      */
-    public void append(final ControlledVocabulary code) {
+    public void append(final CodeList<?> code) {
         if (code != null) {
             appendSeparator();
             final String name = convention.majorVersion() == 1 ? code.name() : Types.getCodeName(code);
@@ -1519,13 +1520,11 @@
             } else {
                 append(number.doubleValue());
             }
-        } else if (value instanceof ControlledVocabulary) {
-            append((ControlledVocabulary) value);
-        } else if (value instanceof Date) {
-            append((Date) value);
-        } else if (value instanceof Boolean) {
-            append((Boolean) value);
-        } else if (value instanceof CharSequence) {
+        }
+        else if (value instanceof CodeList<?>) append((CodeList<?>) value);
+        else if (value instanceof Date)        append((Date)        value);
+        else if (value instanceof Boolean)     append((Boolean)     value);
+        else if (value instanceof CharSequence) {
             append((value instanceof InternationalString) ?
                     ((InternationalString) value).toString(locale) : value.toString(), null);
         } else if (value.getClass().isArray()) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
index 052bf08..4f258bd 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@ -43,6 +43,7 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.ObjectFactory;
 import org.opengis.util.FactoryException;
@@ -74,6 +75,7 @@
 import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.internal.metadata.AxisNames;
 import org.apache.sis.internal.metadata.TransformationAccuracy;
+import org.apache.sis.internal.referencing.ServicesForMetadata;
 import org.apache.sis.internal.referencing.provider.AbstractProvider;
 import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
 import org.apache.sis.internal.referencing.EllipsoidalHeightCombiner;
@@ -971,7 +973,7 @@
             }
             case WKTKeywords.parametric: {
                 if (axes.length != (dimension = 1)) break;
-                return csFactory.createParametricCS(csProperties, axes[0]);
+                return ServicesForMetadata.createParametricCS(csProperties, axes[0], csFactory);
             }
             default: {
                 warning(parent, WKTKeywords.CS, Errors.formatInternational(Errors.Keys.UnknownType_1, type), null);
@@ -1291,14 +1293,16 @@
          */
         FactoryException suppressed = null;
         final CoordinateOperationFactory opFactory = factories.getCoordinateOperationFactory();
-        if (id != null) try {
+        final MathTransformFactory mtFactory = factories.getMathTransformFactory();
+        if (id instanceof ReferenceIdentifier) try {
             // CodeSpace is a mandatory attribute in ID[…] elements, so we do not test for null values.
-            return opFactory.getOperationMethod(id.getCodeSpace() + Constants.DEFAULT_SEPARATOR + id.getCode());
+            return ServicesForMetadata.getOperationMethod(opFactory, mtFactory,
+                    ((ReferenceIdentifier) id).getCodeSpace() + Constants.DEFAULT_SEPARATOR + id.getCode());
         } catch (FactoryException e) {
             suppressed = e;
         }
         try {
-            return opFactory.getOperationMethod(name);
+            return ServicesForMetadata.getOperationMethod(opFactory, mtFactory, name);
         } catch (FactoryException e) {
             if (suppressed != null) {
                 e.addSuppressed(suppressed);
@@ -1503,10 +1507,10 @@
      *
      * @param  mode    {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent  the parent element.
-     * @return the {@code "ParametricDatum"} element as a {@link ParametricDatum} object.
+     * @return the {@code "ParametricDatum"} element as a {@code ParametricDatum} object.
      * @throws ParseException if the {@code "ParametricDatum"} element can not be parsed.
      */
-    private ParametricDatum parseParametricDatum(final int mode, final Element parent) throws ParseException {
+    private Datum parseParametricDatum(final int mode, final Element parent) throws ParseException {
         final Element element = parent.pullElement(mode, WKTKeywords.ParametricDatum, WKTKeywords.PDatum);
         if (element == null) {
             return null;
@@ -1514,7 +1518,7 @@
         final String name = element.pullString("name");
         final DatumFactory datumFactory = factories.getDatumFactory();
         try {
-            return datumFactory.createParametricDatum(parseAnchorAndClose(element, name));
+            return ServicesForMetadata.createParametricDatum(parseAnchorAndClose(element, name), datumFactory);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -2045,7 +2049,7 @@
          * A ParametricCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind ParametricCRS.
          * In the latter case, the datum is null and we have instead DerivingConversion element from a BaseParametricCRS.
          */
-        ParametricDatum datum    = null;
+        Datum           datum    = null;
         SingleCRS       baseCRS  = null;
         Conversion      fromBase = null;
         if (!isBaseCRS) {
@@ -2071,12 +2075,12 @@
         try {
             cs = parseCoordinateSystem(element, WKTKeywords.parametric, 1, false, unit, datum);
             final Map<String,?> properties = parseMetadataAndClose(element, name, datum);
-            if (cs instanceof ParametricCS) {
+            if (cs != null) {
                 final CRSFactory crsFactory = factories.getCRSFactory();
                 if (baseCRS != null) {
                     return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs);
                 }
-                return crsFactory.createParametricCRS(properties, datum, (ParametricCS) cs);
+                return ServicesForMetadata.createParametricCRS(properties, datum, cs, crsFactory);
             }
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
index bdb94d8..3f80751 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
@@ -60,7 +60,7 @@
  * @author  Rueben Schulz (UBC)
  * @version 1.0
  *
- * @see <a href="http://www.geoapi.org/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</a>
+ * @see <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</a>
  *
  * @since 0.6
  * @module
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java
index 30dcb36..c73c256 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java
@@ -34,9 +34,9 @@
 import java.util.function.Consumer;
 import org.opengis.util.FactoryException;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.factory.FactoryDataException;
@@ -251,7 +251,7 @@
              * Identifier should never be null because `WKTDictionary` accepts only definitions having
              * an `ID[…]` or `AUTHORITY[…]` element. A WKT can contain at most one of those elements.
              */
-            final Identifier id = CollectionsExt.first(object.getIdentifiers());
+            final ReferenceIdentifier id = CollectionsExt.first(object.getIdentifiers());
             codespace = id.getCodeSpace();
             version   = id.getVersion();
             value     = object;
@@ -752,7 +752,7 @@
      * @throws FactoryException if parsing failed.
      */
     private IdentifiedObject parseAndAdd(final String codespace, final String version,
-            final String code, final String wkt, final Identifier defaultIdentifier) throws FactoryException
+            final String code, final String wkt, final DefaultIdentifier defaultIdentifier) throws FactoryException
     {
         ArgumentChecks.ensureNonEmpty("code", code);
         ArgumentChecks.ensureNonEmpty("wkt",  wkt);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
index 192319e..00ab54a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
@@ -38,7 +38,6 @@
 import javax.measure.Unit;
 import org.opengis.util.Factory;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.cs.CSFactory;
@@ -57,6 +56,7 @@
 import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
 import org.apache.sis.referencing.ImmutableIdentifier;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
 
 
 /**
@@ -216,9 +216,9 @@
      * <p>This field is transient because this is not yet a public API. The {@code transient}
      * keyword may be removed in a future version if we commit to this API.</p>
      *
-     * @see #setDefaultIdentifier(Identifier)
+     * @see #setDefaultIdentifier(DefaultIdentifier)
      */
-    private transient Identifier defaultIdentifier;
+    private transient DefaultIdentifier defaultIdentifier;
 
     /**
      * WKT fragments that can be inserted in longer WKT strings, or {@code null} if none. Keys are short identifiers
@@ -671,7 +671,7 @@
      *
      * @param  identifier  the default identifier, or {@code null} if none.
      */
-    final void setDefaultIdentifier(final Identifier identifier) {
+    final void setDefaultIdentifier(final DefaultIdentifier identifier) {
         defaultIdentifier = identifier;
     }
 
@@ -976,7 +976,10 @@
         @Override String getFacadeMethod() {return "parse";}
 
         /** Invoked when an identifier need to be supplied to root {@link IdentifiedObject}. */
-        @Override public Object apply(Object key) {return new ImmutableIdentifier(defaultIdentifier);}
+        @Override public Object apply(Object key) {
+            return new ImmutableIdentifier(defaultIdentifier.getAuthority(), defaultIdentifier.getCodeSpace(),
+                    defaultIdentifier.getCode(), defaultIdentifier.getVersion(), defaultIdentifier.getDescription());
+        }
 
         /** Invoked when a root {@link IdentifiedObject} is about to be created. */
         @Override void completeRoot(final Map<String,Object> properties) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
index 061a620..5f711ba 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
@@ -17,12 +17,10 @@
 package org.apache.sis.parameter;
 
 import java.util.Map;
-import java.util.Objects;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlSeeAlso;
 import javax.xml.bind.annotation.XmlSchemaType;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.GeneralParameterDescriptor;
@@ -34,7 +32,6 @@
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Debug;
 
-import static org.apache.sis.util.Utilities.deepEquals;
 import static org.apache.sis.internal.jaxb.referencing.CC_GeneralOperationParameter.DEFAULT_OCCURRENCE;
 
 
@@ -69,7 +66,7 @@
  *     <td class="sep">Also known as “definition”.</td>
  *   </tr>
  *   <tr>
- *     <td>{@link #getDirection()}</td>
+ *     <td>{@code getDirection()}</td>
  *     <td class="sep"></td>
  *     <td class="sep"></td>
  *     <td class="sep">{@code direction}</td>
@@ -146,7 +143,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -156,7 +153,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -227,17 +224,6 @@
     }
 
     /**
-     * Returns an indication if the parameter is an input to the service, an output or both.
-     * The default implementation returns {@link ParameterDirection#IN}.
-     *
-     * @return indication if the parameter is an input or output to the service, or {@code null} if unspecified.
-     */
-    @Override
-    public ParameterDirection getDirection() {
-        return ParameterDirection.IN;
-    }
-
-    /**
      * The minimum number of times that values for this parameter group or parameter are required.
      * A value of 0 means an optional parameter.
      *
@@ -276,9 +262,7 @@
                 default: {
                     final GeneralParameterDescriptor that = (GeneralParameterDescriptor) object;
                     return getMinimumOccurs() == that.getMinimumOccurs() &&
-                           getMaximumOccurs() == that.getMaximumOccurs() &&
-                           Objects.equals(getDirection(), that.getDirection()) &&
-                           deepEquals(getDescription(), that.getDescription(), mode);
+                           getMaximumOccurs() == that.getMaximumOccurs();
                 }
             }
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
index 2d65a27..72e56fe 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
@@ -134,7 +134,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -144,11 +144,11 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java
index e56aaad..5e7f8bd 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java
@@ -24,7 +24,6 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.GeneralParameterDescriptor;
@@ -125,7 +124,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -135,11 +134,11 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>
@@ -320,31 +319,6 @@
     }
 
     /**
-     * Returns an indication if all parameters in this group are inputs to the service, outputs or both.
-     * If this group contains parameters with different direction, then this method returns {@code null}.
-     *
-     * @return indication if all parameters are inputs or outputs to the service, or {@code null} if undetermined.
-     */
-    @Override
-    public ParameterDirection getDirection() {
-        ParameterDirection dir = null;
-        for (final GeneralParameterDescriptor param : descriptors) {
-            final ParameterDirection c = param.getDirection();
-            if (c == null) {
-                return null;
-            }
-            if (c != dir) {
-                if (dir == null) {
-                    dir = c;
-                } else {
-                    return null;
-                }
-            }
-        }
-        return dir;
-    }
-
-    /**
      * Returns all parameters in this group.
      *
      * @return the parameter descriptors in this group.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
index 320aebd..e74d27e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
@@ -40,7 +40,7 @@
 import org.opengis.util.ScopedName;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 import org.opengis.metadata.Identifier;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.ReferenceIdentifier;
@@ -346,7 +346,7 @@
 
     /**
      * Filters names, aliases and identifiers by their code spaces. If the given array is non-null, then the only names,
-     * aliases and identifiers to be formatted are those having a {@link Identifier#getCodeSpace()},
+     * aliases and identifiers to be formatted are those having a {@link ReferenceIdentifier#getCodeSpace()},
      * {@link ScopedName#head()} or {@link GenericName#scope()} value in the given list, unless no name or alias
      * matches this criterion.
      *
@@ -725,8 +725,8 @@
                                 configure((NumberFormat) format, Math.abs(((Number) value).doubleValue()));
                             }
                             text = format.format(value, buffer, dummyFP);
-                        } else if (value instanceof ControlledVocabulary) {
-                            text = Types.getCodeTitle((ControlledVocabulary) value).toString(getLocale());
+                        } else if (value instanceof CodeList<?>) {
+                            text = Types.getCodeTitle((CodeList<?>) value).toString(getLocale());
                         } else if (value instanceof InternationalString) {
                             text = ((InternationalString) value).toString(getLocale());
                         } else {
@@ -851,7 +851,7 @@
             final Set<ReferenceIdentifier> identifiers = object.getIdentifiers();
             if (identifiers != null) {                                              // Paranoiac check.
                 Identifier identifier = null;
-                for (final Identifier candidate : identifiers) {
+                for (final ReferenceIdentifier candidate : identifiers) {
                     if (candidate != null) {                                        // Paranoiac check.
                         if (isPreferredCodespace(candidate.getCodeSpace())) {
                             identifier = candidate;
@@ -872,7 +872,7 @@
              * in the current row and clear the 'name' locale variable. Otherwise, keep the 'name'
              * locale variable in case we found no alias to format.
              */
-            Identifier name = object.getName();
+            ReferenceIdentifier name = object.getName();
             if (name != null) { // Paranoiac check.
                 final String codespace = name.getCodeSpace();
                 if (isPreferredCodespace(codespace)) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterTableRow.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterTableRow.java
index 3b0e965..e755226 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterTableRow.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterTableRow.java
@@ -34,6 +34,7 @@
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.io.wkt.Colors;
 import org.apache.sis.io.wkt.ElementKind;
 import org.apache.sis.util.Characters;
@@ -129,7 +130,7 @@
         values = new ArrayList<>(2);            // In the vast majority of cases, we will have only one value.
         units  = new ArrayList<>(2);
         identifiers = new LinkedHashMap<>();
-        Identifier name = object.getName();
+        ReferenceIdentifier name = object.getName();
         if (name != null) {                                             // Paranoiac check.
             final String codespace = name.getCodeSpace();
             if (preferredCodespaces == null || preferredCodespaces.contains(codespace)) {
@@ -172,9 +173,9 @@
          * Add identifiers (detailed mode only).
          */
         if (!isBrief) {
-            final Collection<? extends Identifier> ids = object.getIdentifiers();
+            final Collection<? extends ReferenceIdentifier> ids = object.getIdentifiers();
             if (ids != null) {                                          // Paranoiac check.
-                for (final Identifier id : ids) {
+                for (final ReferenceIdentifier id : ids) {
                     if (!isDeprecated(id)) {
                         final String codespace = id.getCodeSpace();
                         if (preferredCodespaces == null || preferredCodespaces.contains(codespace)) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java
index fc7ef71..0b4e5a1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java
@@ -846,8 +846,8 @@
                     try {
                         target = destination.parameter(name);
                     } catch (ParameterNotFoundException cause) {
-                        throw new InvalidParameterNameException(Errors.format(
-                                    Errors.Keys.UnexpectedParameter_1, name), cause, name);
+                        throw (InvalidParameterNameException) new InvalidParameterNameException(Errors.format(
+                                    Errors.Keys.UnexpectedParameter_1, name), name).initCause(cause);
                     }
                 } else {
                     target = (ParameterValue<?>) getOrCreate(destination, name, occurrence);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
index 761c47a..9894189 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
@@ -700,7 +700,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link DefaultParameterDescriptorGroup#getName()}</td>
      *   </tr>
      *   <tr>
@@ -710,7 +710,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link DefaultParameterDescriptorGroup#getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -784,8 +784,8 @@
                     cause = e;
                 }
                 if (indices == null) {
-                    throw new InvalidParameterNameException(Errors.format(
-                                Errors.Keys.UnexpectedParameter_1, name), cause, name);
+                    throw (InvalidParameterNameException) new InvalidParameterNameException(Errors.format(
+                                Errors.Keys.UnexpectedParameter_1, name), name).initCause(cause);
                 }
                 matrix.setElement(indices[0], indices[1], ((ParameterValue<?>) param).doubleValue());
             }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
index 92341bf..7083a59 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
@@ -72,6 +72,7 @@
 
 // Branch-dependent imports
 import org.opengis.referencing.ReferenceIdentifier;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
 
 
 /**
@@ -273,7 +274,7 @@
      *     <td>{@link NamedIdentifier#getVersion()} on the {@linkplain #getName() name}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link String}</td>
      *     <td>{@link NamedIdentifier#getDescription()} on the {@linkplain #getName() name}</td>
      *   </tr>
@@ -530,7 +531,14 @@
      */
     @XmlElement(name = "description")
     public InternationalString getDescription() {
-        return (name != null) ? name.getDescription() : null;
+        final ReferenceIdentifier name = getName();
+        if (name instanceof ImmutableIdentifier) {
+            return ((ImmutableIdentifier) name).getDescription();
+        }
+        if (name instanceof DefaultIdentifier) {
+            return ((DefaultIdentifier) name).getDescription();
+        }
+        return null;
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java
index 1f3457a..f342934 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java
@@ -24,7 +24,7 @@
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.referencing.ReferenceSystem;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.metadata.extent.Extent;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.util.ComparisonMode;
@@ -123,7 +123,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -133,7 +133,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
index 5f4fba9..6553561 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
@@ -208,7 +208,7 @@
 
     /**
      * The codespace as a {@code NameSpace} object, or {@code null} if not yet created.
-     * This object is built from the {@value org.opengis.metadata.Identifier#CODESPACE_KEY} value when first needed.
+     * This object is built from the "codespace" value when first needed.
      */
     private transient NameSpace namespace;
 
@@ -406,7 +406,7 @@
      * @since 0.6
      */
     private String getCodeSpace() {
-        return (String) properties.get(Identifier.CODESPACE_KEY);
+        return (String) properties.get(ReferenceIdentifier.CODESPACE_KEY);
     }
 
     /**
@@ -438,7 +438,7 @@
      * @see ImmutableIdentifier#getCodeSpace()
      */
     public B setCodeSpace(final Citation authority, final String codespace) {
-        if (!setProperty(Identifier.CODESPACE_KEY, codespace)) {
+        if (!setProperty(ReferenceIdentifier.CODESPACE_KEY, codespace)) {
             namespace = null;
         }
         setProperty(Identifier.AUTHORITY_KEY, authority);
@@ -454,7 +454,7 @@
      * @since 0.6
      */
     private String getVersion() {
-        return (String) properties.get(Identifier.VERSION_KEY);
+        return (String) properties.get(ReferenceIdentifier.VERSION_KEY);
     }
 
     /**
@@ -475,7 +475,7 @@
      *         once since builder construction or since the last call to a {@code createXXX(…)} method.
      */
     public B setVersion(final String version) {
-        setProperty(Identifier.VERSION_KEY, version);
+        setProperty(ReferenceIdentifier.VERSION_KEY, version);
         return self();
     }
 
@@ -918,7 +918,7 @@
      * or {@code null} if none.
      */
     private InternationalString getDescription() {
-        return (InternationalString) properties.get(Identifier.DESCRIPTION_KEY);
+        return (InternationalString) properties.get("description");
     }
 
     /**
@@ -952,7 +952,7 @@
          * Convert to InternationalString now in order to share the same instance if
          * the same description is used both for an Identifier and an IdentifiedObject.
          */
-        properties.put(Identifier.DESCRIPTION_KEY, Types.toInternationalString(description));
+        properties.put("description", Types.toInternationalString(description));
         return self();
     }
 
@@ -1046,7 +1046,7 @@
         if (cleanup) {
             properties .put(IdentifiedObject.NAME_KEY, null);
             properties .remove(IdentifiedObject.REMARKS_KEY);
-            properties .remove(Identifier.DESCRIPTION_KEY);
+            properties .remove("description");
             properties .remove(AbstractIdentifiedObject.DEPRECATED_KEY);
             aliases    .clear();
             identifiers.clear();
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 a19ea82..b88fabd 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
@@ -49,9 +49,7 @@
 import org.opengis.referencing.operation.OperationNotFoundException;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.extent.Extent;
-import org.opengis.metadata.extent.BoundingPolygon;
 import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.measure.Units;
@@ -92,9 +90,6 @@
 
 import static java.util.logging.Logger.getLogger;
 
-// Branch-dependent imports
-import org.opengis.geometry.Geometry;
-
 
 /**
  * Static methods working on {@linkplain CoordinateReferenceSystem Coordinate Reference Systems}.
@@ -794,16 +789,6 @@
      * If non-null, then the returned envelope will use the same coordinate reference system than the given CRS
      * argument.
      *
-     * <p>This method looks in two places:</p>
-     * <ol>
-     *   <li>First, it checks the {@linkplain org.apache.sis.referencing.crs.AbstractCRS#getDomainOfValidity()
-     *       domain of validity} associated with the given CRS. Only geographic extents that are instances of
-     *       {@link BoundingPolygon} associated to the given CRS are taken in account for this first step.</li>
-     *   <li>If the above step did not found found any bounding polygon, then the
-     *       {@linkplain #getGeographicBoundingBox(CoordinateReferenceSystem) geographic bounding boxes}
-     *       are used as a fallback and transformed to the given CRS.</li>
-     * </ol>
-     *
      * @param  crs  the coordinate reference system, or {@code null}.
      * @return the envelope with coordinates in the given CRS, or {@code null} if none.
      *
@@ -815,37 +800,7 @@
     public static Envelope getDomainOfValidity(final CoordinateReferenceSystem crs) {
         Envelope envelope = null;
         GeneralEnvelope merged = null;
-        if (crs != null) {
-            final Extent domainOfValidity = crs.getDomainOfValidity();
-            if (domainOfValidity != null) {
-                for (final GeographicExtent extent : domainOfValidity.getGeographicElements()) {
-                    if (extent instanceof BoundingPolygon && !Boolean.FALSE.equals(extent.getInclusion())) {
-                        for (final Geometry geometry : ((BoundingPolygon) extent).getPolygons()) {
-                            final Envelope candidate = geometry.getEnvelope();
-                            if (candidate != null) {
-                                final CoordinateReferenceSystem sourceCRS = candidate.getCoordinateReferenceSystem();
-                                if (sourceCRS == null || Utilities.equalsIgnoreMetadata(sourceCRS, crs)) {
-                                    if (envelope == null) {
-                                        envelope = candidate;
-                                    } else {
-                                        if (merged == null) {
-                                            envelope = merged = new GeneralEnvelope(envelope);
-                                        }
-                                        merged.add(envelope);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        /*
-         * If no envelope was found, uses the geographic bounding box as a fallback. We will
-         * need to transform it from WGS84 to the supplied CRS. This step was not required in
-         * the previous block because the latter selected only envelopes in the right CRS.
-         */
-        if (envelope == null) {
+        /* if (envelope == null) */ {   // Condition needed on other branches but not on trunk.
             final GeographicBoundingBox bounds = getGeographicBoundingBox(crs);
             if (bounds != null && !Boolean.FALSE.equals(bounds.getInclusion())) {
                 /*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
index be3c1e1..cbf7ed7 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
@@ -30,6 +30,7 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.CompoundCRS;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.ConcatenatedOperation;
@@ -742,11 +743,16 @@
         if (identifier == null) {
             return null;
         }
-        String cs = identifier.getCodeSpace();
+        String cs = null;
+        if (identifier instanceof ReferenceIdentifier) {
+            cs = ((ReferenceIdentifier) identifier).getCodeSpace();
+        }
         if (cs == null || cs.isEmpty()) {
             cs = Identifiers.getIdentifier(identifier.getAuthority(), true);
         }
-        return NameMeaning.toURN(type, cs, identifier.getVersion(), identifier.getCode());
+        return NameMeaning.toURN(type, cs,
+                (identifier instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) identifier).getVersion() : null,
+                identifier.getCode());
     }
 
     /**
@@ -756,7 +762,7 @@
      * <ul>
      *   <li>If the given identifier implements the {@link GenericName} interface,
      *       then this method delegates to the {@link GenericName#toString()} method.</li>
-     *   <li>Otherwise if the given identifier has a {@linkplain Identifier#getCodeSpace() code space},
+     *   <li>Otherwise if the given identifier has a {@linkplain ReferenceIdentifier#getCodeSpace() code space},
      *       then formats the identifier as "{@code codespace:code}".</li>
      *   <li>Otherwise if the given identifier has an {@linkplain Identifier#getAuthority() authority},
      *       then formats the identifier as "{@code authority:code}".</li>
@@ -783,7 +789,10 @@
             return identifier.toString();
         }
         final String code = identifier.getCode();
-        String cs = identifier.getCodeSpace();
+        String cs = null;
+        if (identifier instanceof ReferenceIdentifier) {
+            cs = ((ReferenceIdentifier) identifier).getCodeSpace();
+        }
         if (cs == null || cs.isEmpty()) {
             cs = Citations.toCodeSpace(identifier.getAuthority());
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/ImmutableIdentifier.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/ImmutableIdentifier.java
index 6166e4f..da166c7 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/ImmutableIdentifier.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/ImmutableIdentifier.java
@@ -28,6 +28,7 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.metadata.TitleProperty;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.internal.metadata.Identifiers;
 import org.apache.sis.internal.metadata.NameMeaning;
@@ -119,6 +120,12 @@
     private static final long serialVersionUID = 1804606250548055829L;
 
     /**
+     * Key for the {@value} property in the map to be given to the constructor.
+     * This can be used for setting the value to be returned by {@link #getDescription()}.
+     */
+    public static final String DESCRIPTION_KEY = "description";
+
+    /**
      * The person or party responsible for maintenance of the namespace, or {@code null} if not available.
      *
      * @see #getAuthority()
@@ -160,15 +167,19 @@
      *
      * @param identifier  the identifier to copy.
      *
-     * @see #castOrCopy(Identifier)
+     * @see #castOrCopy(ReferenceIdentifier)
      */
-    public ImmutableIdentifier(final Identifier identifier) {
+    public ImmutableIdentifier(final ReferenceIdentifier identifier) {
         ensureNonNull("identifier", identifier);
         code        = identifier.getCode();
         codeSpace   = identifier.getCodeSpace();
         authority   = identifier.getAuthority();
         version     = identifier.getVersion();
-        description = identifier.getDescription();
+        if (identifier instanceof DefaultIdentifier) {
+            description = ((DefaultIdentifier) identifier).getDescription();
+        } else {
+            description = null;
+        }
         validate(null);
     }
 
@@ -225,22 +236,22 @@
      *     <td>{@link #getCode()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#CODESPACE_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceIdentifier#CODESPACE_KEY}</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getCodeSpace()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#AUTHORITY_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceIdentifier#AUTHORITY_KEY}</td>
      *     <td>{@link String} or {@link Citation}</td>
      *     <td>{@link #getAuthority()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#VERSION_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceIdentifier#VERSION_KEY}</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getVersion()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>{@value #DESCRIPTION_KEY}</td>
      *     <td>{@link String} or {@link InternationalString}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>
@@ -327,7 +338,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code ImmutableIdentifier}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code ImmutableIdentifier} instance is created using the
-     *       {@linkplain #ImmutableIdentifier(Identifier) copy constructor} and returned.
+     *       {@linkplain #ImmutableIdentifier(ReferenceIdentifier) copy constructor} and returned.
      *       Note that this is a <cite>shallow</cite> copy operation, since the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -336,7 +347,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static ImmutableIdentifier castOrCopy(final Identifier object) {
+    public static ImmutableIdentifier castOrCopy(final ReferenceIdentifier object) {
         if (object == null || object instanceof ImmutableIdentifier) {
             return (ImmutableIdentifier) object;
         }
@@ -408,7 +419,6 @@
      *
      * @since 0.5
      */
-    @Override
     public InternationalString getDescription() {
         return description;
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
index 80cda06..2fcf218 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
@@ -94,7 +94,7 @@
  * @since 0.4
  * @module
  */
-public class NamedIdentifier extends ImmutableIdentifier implements GenericName, ReferenceIdentifier {
+public class NamedIdentifier extends ImmutableIdentifier implements GenericName {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -122,9 +122,9 @@
      *
      * @param  identifier  the identifier to copy.
      *
-     * @see #castOrCopy(Identifier)
+     * @see #castOrCopy(ReferenceIdentifier)
      */
-    public NamedIdentifier(final Identifier identifier) {
+    public NamedIdentifier(final ReferenceIdentifier identifier) {
         super(identifier);
         if (identifier instanceof GenericName) {
             name = (GenericName) identifier;
@@ -142,7 +142,7 @@
      * @see #castOrCopy(GenericName)
      */
     public NamedIdentifier(final GenericName name) {
-        super(name instanceof Identifier ? (Identifier) name : new NameToIdentifier(name));
+        super(name instanceof ReferenceIdentifier ? (ReferenceIdentifier) name : new NameToIdentifier(name));
         this.name = name;
         isNameSupplied = true;
     }
@@ -173,7 +173,7 @@
      *     <td>{@link #getCode()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#CODESPACE_KEY}</td>
+     *     <td>"codespace"</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getCodeSpace()}</td>
      *   </tr>
@@ -183,12 +183,12 @@
      *     <td>{@link #getAuthority()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#VERSION_KEY}</td>
+     *     <td>"version"</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getVersion()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link String} or {@link InternationalString}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>
@@ -340,7 +340,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code NamedIdentifier}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code NamedIdentifier} instance is created using the
-     *       {@linkplain #NamedIdentifier(Identifier) copy constructor} and returned.
+     *       {@linkplain #NamedIdentifier(ReferenceIdentifier) copy constructor} and returned.
      *       Note that this is a <cite>shallow</cite> copy operation, since the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -351,7 +351,7 @@
      *
      * @since 1.0
      */
-    public static NamedIdentifier castOrCopy(final Identifier object) {
+    public static NamedIdentifier castOrCopy(final ReferenceIdentifier object) {
         if (object == null || object instanceof NamedIdentifier) {
             return (NamedIdentifier) object;
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
index 656f309..ed9ea2b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
@@ -25,7 +25,7 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlSeeAlso;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.cs.AffineCS;
 import org.opengis.referencing.cs.CartesianCS;
@@ -143,7 +143,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -153,7 +153,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -346,7 +346,7 @@
                  * For example "EPSG:WGS 84" will become simply "WGS 84".
                  */
                 Map<String,?> properties = IdentifiedObjects.getProperties(this, IDENTIFIERS_KEY);
-                Identifier name = getName();
+                ReferenceIdentifier name = getName();
                 if (name.getCodeSpace() != null || name.getAuthority() != null) {
                     name = new NamedIdentifier(null, name.getCode());
                     final Map<String,Object> copy = new HashMap<>(properties);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
index e2597aa..6f9e8e2 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
@@ -35,7 +35,6 @@
 import org.opengis.referencing.crs.EngineeringCRS;
 import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.crs.TemporalCRS;
-import org.opengis.referencing.crs.ParametricCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.apache.sis.referencing.cs.AxesConvention;
@@ -156,7 +155,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -166,7 +165,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
index f8e57da..4f4b1f0 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
@@ -58,9 +58,8 @@
 import org.apache.sis.util.ComparisonMode;
 
 // Branch-dependent imports
-import org.opengis.referencing.cs.ParametricCS;
-import org.opengis.referencing.crs.ParametricCRS;
-import org.opengis.referencing.datum.ParametricDatum;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
 
 
 /**
@@ -296,7 +295,7 @@
                 case WKTKeywords.GeodeticCRS:   return new Geodetic  (properties, (GeodeticCRS)   baseCRS, conversion,                derivedCS);
                 case WKTKeywords.VerticalCRS:   return new Vertical  (properties, (VerticalCRS)   baseCRS, conversion,   (VerticalCS) derivedCS);
                 case WKTKeywords.TimeCRS:       return new Temporal  (properties, (TemporalCRS)   baseCRS, conversion,       (TimeCS) derivedCS);
-                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, conversion, (ParametricCS) derivedCS);
+                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, conversion, (DefaultParametricCS) derivedCS);
                 case WKTKeywords.EngineeringCRS: {
                     /*
                      * This case may happen for baseCRS of kind GeodeticCRS, ProjectedCRS or EngineeringCRS.
@@ -351,7 +350,7 @@
                 case WKTKeywords.GeodeticCRS:   return new Geodetic  (properties, (GeodeticCRS)   baseCRS, interpolationCRS, method, baseToDerived,                derivedCS);
                 case WKTKeywords.VerticalCRS:   return new Vertical  (properties, (VerticalCRS)   baseCRS, interpolationCRS, method, baseToDerived,  (VerticalCS)  derivedCS);
                 case WKTKeywords.TimeCRS:       return new Temporal  (properties, (TemporalCRS)   baseCRS, interpolationCRS, method, baseToDerived,      (TimeCS)  derivedCS);
-                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, interpolationCRS, method, baseToDerived, (ParametricCS) derivedCS);
+                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, interpolationCRS, method, baseToDerived, (DefaultParametricCS) derivedCS);
                 case WKTKeywords.EngineeringCRS: {
                     if (baseCRS instanceof EngineeringCRS) {
                         // See the comment in create(Map, SingleCRS, Conversion, CoordinateSystem)
@@ -616,7 +615,7 @@
             return WKTKeywords.VerticalCRS;
         } else if (TemporalCRS.class.isAssignableFrom(type) && derivedCS instanceof TimeCS) {
             return WKTKeywords.TimeCRS;
-        } else if (ParametricCRS.class.isAssignableFrom(type) && derivedCS instanceof ParametricCS) {
+        } else if (ParametricCRS.class.isAssignableFrom(type) && derivedCS instanceof DefaultParametricCS) {
             return WKTKeywords.ParametricCRS;
         } else if (ProjectedCRS.class.isAssignableFrom(type) || EngineeringCRS.class.isAssignableFrom(type)) {
             return WKTKeywords.EngineeringCRS;
@@ -798,32 +797,32 @@
         }
 
         /** Creates a new parametric CRS from the given properties. */
-        Parametric(Map<String,?> properties, ParametricCRS baseCRS, Conversion conversion, ParametricCS derivedCS) {
+        Parametric(Map<String,?> properties, ParametricCRS baseCRS, Conversion conversion, DefaultParametricCS derivedCS) {
             super(properties, baseCRS, conversion, derivedCS);
         }
 
         /** Creates a new parametric CRS from the given properties. */
         Parametric(Map<String,?> properties, ParametricCRS baseCRS, CoordinateReferenceSystem interpolationCRS,
-                OperationMethod method, MathTransform baseToDerived, ParametricCS derivedCS)
+                OperationMethod method, MathTransform baseToDerived, DefaultParametricCS derivedCS)
         {
             super(properties, baseCRS, interpolationCRS, method, baseToDerived, derivedCS);
         }
 
         /** Returns the datum of the base parametric CRS. */
-        @Override public ParametricDatum getDatum() {
-            return (ParametricDatum) super.getDatum();
+        @Override public DefaultParametricDatum getDatum() {
+            return (DefaultParametricDatum) super.getDatum();
         }
 
         /** Returns the coordinate system given at construction time. */
-        @Override public ParametricCS getCoordinateSystem() {
-            return (ParametricCS) super.getCoordinateSystem();
+        @Override public DefaultParametricCS getCoordinateSystem() {
+            return (DefaultParametricCS) super.getCoordinateSystem();
         }
 
         /** Returns a coordinate reference system of the same type than this CRS but with different axes. */
         @Override AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem derivedCS) {
             final Conversion conversionFromBase = getConversionFromBase();
             return new Parametric(properties, (ParametricCRS) conversionFromBase.getSourceCRS(),
-                    conversionFromBase, (ParametricCS) derivedCS);
+                    conversionFromBase, (DefaultParametricCS) derivedCS);
         }
 
         /** Returns the WKT keyword for this derived CRS type. */
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
index 8d8965e..586b204 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
@@ -104,7 +104,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -114,7 +114,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
index e9256b9..19e152a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
@@ -109,7 +109,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -119,7 +119,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeographicCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
index b6f8bf1..bfb2b4f 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
@@ -20,7 +20,7 @@
 import java.util.HashMap;
 import java.util.Arrays;
 import javax.xml.bind.annotation.XmlTransient;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.GeographicCRS;
@@ -124,7 +124,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -134,7 +134,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -259,7 +259,7 @@
         if (axis.getMinimumValue() == Longitude.MIN_VALUE &&
             axis.getMaximumValue() == Longitude.MAX_VALUE)    // For excluding the AxesConvention.POSITIVE_RANGE case.
         {
-            for (final Identifier identifier : super.getIdentifiers()) {
+            for (final ReferenceIdentifier identifier : super.getIdentifiers()) {
                 if (EPSG.equals(identifier.getCodeSpace())) try {
                     final int i = Arrays.binarySearch(EPSG_CODES, Short.parseShort(identifier.getCode()));
                     if (i >= 0) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultImageCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultImageCRS.java
index d307556..da89ec7 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultImageCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultImageCRS.java
@@ -97,7 +97,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -107,7 +107,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultParametricCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultParametricCRS.java
index 8e83767..70e3e9a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultParametricCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultParametricCRS.java
@@ -30,9 +30,8 @@
 import static org.apache.sis.referencing.crs.AbstractCRS.isBaseCRS;
 
 // Branch-dependent imports
-import org.opengis.referencing.cs.ParametricCS;
-import org.opengis.referencing.crs.ParametricCRS;
-import org.opengis.referencing.datum.ParametricDatum;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
 
 
 /**
@@ -76,11 +75,11 @@
      * The datum.
      *
      * <p><b>Consider this field as final!</b>
-     * This field is modified only at unmarshalling time by {@link #setDatum(ParametricDatum)}</p>
+     * This field is modified only at unmarshalling time by {@code setDatum(ParametricDatum)}</p>
      *
      * @see #getDatum()
      */
-    private ParametricDatum datum;
+    private DefaultParametricDatum datum;
 
     /**
      * Creates a coordinate reference system from the given properties, datum and coordinate system.
@@ -127,15 +126,17 @@
      *   </tr>
      * </table>
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter types may be changed to
+     * {@code org.opengis.referencing.datum.ParametricDatum} and {@code org.opengis.referencing.cs.ParametricCS}
+     * Those change are pending GeoAPI revision.</div>
+     *
      * @param  properties  the properties to be given to the coordinate reference system.
      * @param  datum       the datum.
      * @param  cs          the coordinate system.
-     *
-     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createParametricCRS(Map, ParametricDatum, ParametricCS)
      */
     public DefaultParametricCRS(final Map<String,?> properties,
-                                final ParametricDatum datum,
-                                final ParametricCS    cs)
+                                final DefaultParametricDatum datum,
+                                final DefaultParametricCS cs)
     {
         super(properties, cs);
         ensureNonNull("datum", datum);
@@ -149,54 +150,24 @@
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  crs  the coordinate reference system to copy.
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter type may be changed
+     * to {@code org.opengis.referencing.crs.ParametricCRS}. This change is pending GeoAPI revision.</div>
      *
-     * @see #castOrCopy(ParametricCRS)
+     * @param  crs  the coordinate reference system to copy.
      */
-    protected DefaultParametricCRS(final ParametricCRS crs) {
+    protected DefaultParametricCRS(final DefaultParametricCRS crs) {
         super(crs);
         datum = crs.getDatum();
     }
 
     /**
-     * Returns a SIS coordinate reference system implementation with the same values than the given
-     * arbitrary implementation. If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultParametricCRS castOrCopy(final ParametricCRS object) {
-        return (object == null || object instanceof DefaultParametricCRS)
-                ? (DefaultParametricCRS) object : new DefaultParametricCRS(object);
-    }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The SIS implementation returns {@code ParametricCRS.class}.
-     *
-     * <div class="note"><b>Note for implementers:</b>
-     * Subclasses usually do not need to override this method since GeoAPI does not define {@code ParametricCRS}
-     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
-     * own set of interfaces.</div>
-     *
-     * @return {@code ParametricCRS.class} or a user-defined sub-interface.
-     */
-    @Override
-    public Class<? extends ParametricCRS> getInterface() {
-        return ParametricCRS.class;
-    }
-
-    /**
      * Returns the datum.
      *
      * @return the datum.
      */
     @Override
     @XmlElement(name = "parametricDatum", required = true)
-    public ParametricDatum getDatum() {
+    public DefaultParametricDatum getDatum() {
         return datum;
     }
 
@@ -207,8 +178,8 @@
      */
     @Override
     @XmlElement(name = "parametricCS", required = true)
-    public ParametricCS getCoordinateSystem() {
-        return (ParametricCS) super.getCoordinateSystem();
+    public DefaultParametricCS getCoordinateSystem() {
+        return (DefaultParametricCS) super.getCoordinateSystem();
     }
 
     /**
@@ -226,7 +197,7 @@
      */
     @Override
     final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        return new DefaultParametricCRS(properties, datum, (ParametricCS) cs);
+        return new DefaultParametricCRS(properties, datum, (DefaultParametricCS) cs);
     }
 
     /**
@@ -281,7 +252,7 @@
      *
      * @see #getDatum()
      */
-    private void setDatum(final ParametricDatum value) {
+    private void setDatum(final DefaultParametricDatum value) {
         if (datum == null) {
             datum = value;
         } else {
@@ -294,7 +265,7 @@
      *
      * @see #getCoordinateSystem()
      */
-    private void setCoordinateSystem(final ParametricCS cs) {
+    private void setCoordinateSystem(final DefaultParametricCS cs) {
         setCoordinateSystem("parametricCS", cs);
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
index b1f110f..195b1a8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
@@ -127,7 +127,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -137,7 +137,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultVerticalCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
index 159aa74..8d6a65b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
@@ -95,7 +95,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -105,7 +105,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/ParametricCRS.java
similarity index 61%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/ParametricCRS.java
index 2984463..b1adba1 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/ParametricCRS.java
@@ -14,24 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.referencing.crs;
 
-import org.opengis.style.Symbolizer;
+import org.opengis.referencing.crs.SingleCRS;
+
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
+ * Place-holder for an interface not yet present in GeoAPI 3.0.
  *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
  * @module
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+interface ParametricCRS extends SingleCRS {
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
index cd5fef4..0aaf5f5 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
@@ -142,7 +142,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -152,7 +152,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultAffineCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultAffineCS.java
index 1d632bc..b163e6a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultAffineCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultAffineCS.java
@@ -86,7 +86,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -96,7 +96,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java
index 08259fe..4b347f2 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java
@@ -95,7 +95,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -105,7 +105,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCompoundCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCompoundCS.java
index f5e023e..67b3aad 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCompoundCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCompoundCS.java
@@ -82,7 +82,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -92,7 +92,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
index 97c28a5..2517567 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
@@ -259,7 +259,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -269,7 +269,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
index f753a20..f0d4009 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
@@ -88,7 +88,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -98,7 +98,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
index 5982a82..1f615a3 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
@@ -88,7 +88,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -98,7 +98,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java
index 9833869..e3115dc 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java
@@ -85,7 +85,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -95,7 +95,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultParametricCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultParametricCS.java
index 6b24b18..dca9747 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultParametricCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultParametricCS.java
@@ -21,9 +21,6 @@
 import javax.xml.bind.annotation.XmlType;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 
-// Branch-dependent imports
-import org.opengis.referencing.cs.ParametricCS;
-
 
 /**
  * A 1-dimensional coordinate system for parametric values or functions.
@@ -56,7 +53,7 @@
  */
 @XmlType(name = "ParametricCSType")
 @XmlRootElement(name = "ParametricCS")
-public class DefaultParametricCS extends AbstractCS implements ParametricCS {
+public class DefaultParametricCS extends AbstractCS {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -122,46 +119,16 @@
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter type may be changed
+     * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  cs  the coordinate system to copy.
-     *
-     * @see #castOrCopy(ParametricCS)
      */
-    protected DefaultParametricCS(final ParametricCS cs) {
+    protected DefaultParametricCS(final DefaultParametricCS cs) {
         super(cs);
     }
 
     /**
-     * Returns a SIS coordinate system implementation with the same values than the given arbitrary implementation.
-     * If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultParametricCS castOrCopy(final ParametricCS object) {
-        return (object == null) || (object instanceof DefaultParametricCS)
-                ? (DefaultParametricCS) object : new DefaultParametricCS(object);
-    }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The SIS implementation returns {@code ParametricCS.class}.
-     *
-     * <div class="note"><b>Note for implementers:</b>
-     * Subclasses usually do not need to override this method since GeoAPI does not define {@code ParametricCS}
-     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with
-     * their own set of interfaces.</div>
-     *
-     * @return {@code ParametricCS.class} or a user-defined sub-interface.
-     */
-    @Override
-    public Class<? extends ParametricCS> getInterface() {
-        return ParametricCS.class;
-    }
-
-    /**
      * {@inheritDoc}
      *
      * @return {@inheritDoc}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultPolarCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultPolarCS.java
index f4557d4..5012d50 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultPolarCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultPolarCS.java
@@ -88,7 +88,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -98,7 +98,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultSphericalCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultSphericalCS.java
index 59b3997..0a11fd6 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultSphericalCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultSphericalCS.java
@@ -91,7 +91,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -101,7 +101,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultTimeCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultTimeCS.java
index e0baaee..0963d7a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultTimeCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultTimeCS.java
@@ -88,7 +88,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -98,7 +98,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
index ae8b520..f6fc9a1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
@@ -79,7 +79,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -89,7 +89,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultVerticalCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultVerticalCS.java
index 6366d1e..f80ec96 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultVerticalCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultVerticalCS.java
@@ -99,7 +99,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -109,7 +109,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
index 9536826..3629fc4 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
@@ -25,10 +25,10 @@
 import javax.xml.bind.annotation.XmlSeeAlso;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.util.iso.Types;
@@ -174,7 +174,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -184,7 +184,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java
index 3d48028..4c36686 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java
@@ -27,8 +27,8 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
 import org.opengis.referencing.datum.Ellipsoid;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.internal.jaxb.gml.Measure;
@@ -175,7 +175,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -185,7 +185,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
index bddab2c..464b0e7 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
@@ -21,7 +21,7 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.EngineeringDatum;
 import org.apache.sis.internal.referencing.WKTKeywords;
 import org.apache.sis.io.wkt.Formatter;
@@ -69,7 +69,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -79,7 +79,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
index 35d1271..db65e4b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
@@ -26,8 +26,8 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
 import org.opengis.metadata.extent.Extent;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.datum.Ellipsoid;
@@ -207,7 +207,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -217,7 +217,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultImageDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultImageDatum.java
index ddae660..e46bb6e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultImageDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultImageDatum.java
@@ -23,7 +23,7 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.ImageDatum;
 import org.opengis.referencing.datum.PixelInCell;
 import org.apache.sis.internal.referencing.WKTKeywords;
@@ -86,7 +86,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -96,7 +96,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultParametricDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultParametricDatum.java
index c9c9200..7bddff4 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultParametricDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultParametricDatum.java
@@ -23,9 +23,6 @@
 import org.apache.sis.internal.referencing.WKTKeywords;
 import org.apache.sis.io.wkt.Formatter;
 
-// Branch-dependent imports
-import org.opengis.referencing.datum.ParametricDatum;
-
 
 /**
  * Defines the origin of a parametric coordinate reference system.
@@ -36,7 +33,7 @@
  *
  * <ol>
  *   <li>Create a {@code ParametricDatum} from an identifier in a database by invoking
- *       {@link org.opengis.referencing.datum.DatumAuthorityFactory#createParametricDatum(String)}.</li>
+ *       {@code DatumAuthorityFactory.createParametricDatum(String)}.</li>
  *   <li>Create a {@code ParametricDatum} by invoking the {@code DatumFactory.createParametricDatum(…)} method,
  *       (implemented for example by {@link org.apache.sis.referencing.factory.GeodeticObjectFactory}).</li>
  *   <li>Create a {@code DefaultParametricDatum} by invoking the
@@ -60,7 +57,7 @@
  */
 @XmlType(name = "ParametricDatumType")
 @XmlRootElement(name = "ParametricDatum")
-public class DefaultParametricDatum extends AbstractDatum implements ParametricDatum {
+public class DefaultParametricDatum extends AbstractDatum {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -135,46 +132,16 @@
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter type may be changed
+     * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div>
+     *
      * @param  datum  the datum to copy.
-     *
-     * @see #castOrCopy(ParametricDatum)
      */
-    protected DefaultParametricDatum(final ParametricDatum datum) {
+    protected DefaultParametricDatum(final DefaultParametricDatum datum) {
         super(datum);
     }
 
     /**
-     * Returns a SIS datum implementation with the same values than the given arbitrary implementation.
-     * If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultParametricDatum castOrCopy(final ParametricDatum object) {
-        return (object == null) || (object instanceof DefaultParametricDatum) ?
-                (DefaultParametricDatum) object : new DefaultParametricDatum(object);
-    }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The SIS implementation returns {@code ParametricDatum.class}.
-     *
-     * <div class="note"><b>Note for implementers:</b>
-     * Subclasses usually do not need to override this method since GeoAPI does not define {@code TemporalDatum}
-     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
-     * own set of interfaces.</div>
-     *
-     * @return {@code ParametricDatum.class} or a user-defined sub-interface.
-     */
-    @Override
-    public Class<? extends ParametricDatum> getInterface() {
-        return ParametricDatum.class;
-    }
-
-    /**
      * Formats this datum as a <cite>Well Known Text</cite> {@code ParametricDatum[…]} element.
      *
      * <div class="note"><b>Compatibility note:</b>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
index c0294a3..2673e02 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
@@ -25,7 +25,7 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.crs.GeneralDerivedCRS;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
@@ -128,7 +128,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -138,7 +138,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultTemporalDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
index 9e7f2ce..ac8a5e8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
@@ -26,7 +26,7 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.TemporalDatum;
 import org.apache.sis.internal.referencing.WKTKeywords;
 import org.apache.sis.internal.jaxb.gml.UniversalTimeAdapter;
@@ -112,7 +112,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -122,7 +122,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultVerticalDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
index 919af8b..eb5aa19 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
@@ -23,7 +23,7 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.VerticalDatum;
 import org.opengis.referencing.datum.VerticalDatumType;
 import org.apache.sis.io.wkt.Formatter;
@@ -113,7 +113,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -123,7 +123,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -228,7 +228,7 @@
     private VerticalDatumType type() {
         VerticalDatumType t = type;
         if (t == null) {
-            final Identifier name = super.getName();
+            final ReferenceIdentifier name = super.getName();
             type = t = VerticalDatumTypes.guess(name != null ? name.getCode() : null, super.getAlias(), null);
         }
         return t;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index fd11ee2..90bcf82 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
@@ -40,6 +40,11 @@
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Classes;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.crs.DefaultParametricCRS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+
 
 /**
  * Creates geodetic objects from codes defined by an authority.
@@ -404,6 +409,9 @@
      * The default implementation delegates to {@link #createCoordinateReferenceSystem(String)} and casts the result.
      * If the result can not be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.crs.ParametricCRS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  code  value allocated by authority.
      * @return the coordinate reference system for the given code.
      * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
@@ -411,8 +419,8 @@
      *
      * @see org.apache.sis.referencing.crs.DefaultParametricCRS
      */
-    public ParametricCRS createParametricCRS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
-        return cast(ParametricCRS.class, createCoordinateReferenceSystem(code), code);
+    public DefaultParametricCRS createParametricCRS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(DefaultParametricCRS.class, createCoordinateReferenceSystem(code), code);
     }
 
     /**
@@ -644,6 +652,9 @@
      * The default implementation delegates to {@link #createDatum(String)} and casts the result.
      * If the result can not be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div>
+     *
      * @param  code  value allocated by authority.
      * @return the datum for the given code.
      * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
@@ -651,8 +662,8 @@
      *
      * @see org.apache.sis.referencing.datum.DefaultParametricDatum
      */
-    public ParametricDatum createParametricDatum(final String code) throws NoSuchAuthorityCodeException, FactoryException {
-        return cast(ParametricDatum.class, createDatum(code), code);
+    public DefaultParametricDatum createParametricDatum(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(DefaultParametricDatum.class, createDatum(code), code);
     }
 
     /**
@@ -930,6 +941,9 @@
      * The default implementation delegates to {@link #createCoordinateSystem(String)} and casts the result.
      * If the result can not be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  code  value allocated by authority.
      * @return the coordinate system for the given code.
      * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
@@ -937,8 +951,8 @@
      *
      * @see org.apache.sis.referencing.cs.DefaultParametricCS
      */
-    public ParametricCS createParametricCS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
-        return cast(ParametricCS.class, createCoordinateSystem(code), code);
+    public DefaultParametricCS createParametricCS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(DefaultParametricCS.class, createCoordinateSystem(code), code);
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
index dd78309..48b7e4a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
@@ -116,17 +116,17 @@
  *     <td>{@link NamedIdentifier#getCode()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr>
  *   <tr>
- *     <td>{@value org.opengis.metadata.Identifier#CODESPACE_KEY}</td>
+ *     <td>"codespace"</td>
  *     <td>{@link String}</td>
  *     <td>{@link NamedIdentifier#getCodeSpace()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr>
  *   <tr>
- *     <td>{@value org.opengis.metadata.Identifier#VERSION_KEY}</td>
+ *     <td>"version"</td>
  *     <td>{@link String}</td>
  *     <td>{@link NamedIdentifier#getVersion()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr>
  *   <tr>
- *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+ *     <td>"description"</td>
  *     <td>{@link String}</td>
  *     <td>{@link NamedIdentifier#getDescription()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr>
@@ -1058,17 +1058,21 @@
      *
      * The default implementation creates a {@link DefaultParametricCRS} instance.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter types may be changed to
+     * {@code org.opengis.referencing.datum.ParametricDatum} and {@code org.opengis.referencing.cs.ParametricCS},
+     * and the return type may be changed to {@code org.opengis.referencing.crs.ParametricCRS}.
+     * Those change are pending GeoAPI revision.</div>
+     *
      * @param  properties  name and other properties to give to the new object.
      * @param  datum       the parametric datum to use in created CRS.
      * @param  cs          the parametric coordinate system for the created CRS.
      * @throws FactoryException if the object creation failed.
      *
-     * @see DefaultParametricCRS#DefaultParametricCRS(Map, ParametricDatum, ParametricCS)
+     * @see DefaultParametricCRS#DefaultParametricCRS(Map, DefaultParametricDatum, DefaultParametricCS)
      * @see GeodeticAuthorityFactory#createParametricCRS(String)
      */
-    @Override
-    public ParametricCRS createParametricCRS(final Map<String,?> properties,
-            final ParametricDatum datum, final ParametricCS cs) throws FactoryException
+    public DefaultParametricCRS createParametricCRS(final Map<String,?> properties,
+            final DefaultParametricDatum datum, final DefaultParametricCS cs) throws FactoryException
     {
         final DefaultParametricCRS crs;
         try {
@@ -1083,14 +1087,16 @@
      * Creates a parametric datum.
      * The default implementation creates a {@link DefaultParametricDatum} instance.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div>
+     *
      * @param  properties  name and other properties to give to the new object.
      * @throws FactoryException if the object creation failed.
      *
      * @see DefaultParametricDatum#DefaultParametricDatum(Map)
      * @see GeodeticAuthorityFactory#createParametricDatum(String)
      */
-    @Override
-    public ParametricDatum createParametricDatum(final Map<String,?> properties)
+    public DefaultParametricDatum createParametricDatum(final Map<String,?> properties)
             throws FactoryException
     {
         final DefaultParametricDatum datum;
@@ -1114,6 +1120,9 @@
      *
      * The default implementation creates a {@link DefaultParametricCS} instance.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  properties  name and other properties to give to the new object.
      * @param  axis        the single axis.
      * @throws FactoryException if the object creation failed.
@@ -1121,8 +1130,7 @@
      * @see DefaultParametricCS#DefaultParametricCS(Map, CoordinateSystemAxis)
      * @see GeodeticAuthorityFactory#createParametricCS(String)
      */
-    @Override
-    public ParametricCS createParametricCS(Map<String, ?> properties, CoordinateSystemAxis axis) throws FactoryException {
+    public DefaultParametricCS createParametricCS(Map<String, ?> properties, CoordinateSystemAxis axis) throws FactoryException {
         final DefaultParametricCS cs;
         try {
             cs = new DefaultParametricCS(complete(properties), axis);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 01742ed..fcb1c87 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -53,7 +53,6 @@
 import org.opengis.util.InternationalString;
 import org.opengis.util.FactoryException;
 import org.opengis.util.NoSuchIdentifierException;
-import org.opengis.metadata.Identifier;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.OnLineFunction;
@@ -124,6 +123,11 @@
 import static org.apache.sis.internal.util.StandardDateFormat.UTC;
 import static org.apache.sis.internal.referencing.ServicesForMetadata.CONNECTION;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+import org.apache.sis.internal.referencing.ServicesForMetadata;
+
 
 /**
  * <cite>Data Access Object</cite> (DAO) creating geodetic objects from a JDBC connection to an EPSG database.
@@ -1590,10 +1594,10 @@
                      *   PARAMETRIC CRS
                      * ---------------------------------------------------------------------- */
                     case "parametric": {
-                        final ParametricCS    cs    = owner.createParametricCS   (getString(code, result, 8));
-                        final ParametricDatum datum = owner.createParametricDatum(getString(code, result, 9));
-                        crs = crsFactory.createParametricCRS(createProperties("Coordinate Reference System",
-                                name, epsg, area, scope, remarks, deprecated), datum, cs);
+                        final DefaultParametricCS    cs    = owner.createParametricCS   (getString(code, result, 8));
+                        final DefaultParametricDatum datum = owner.createParametricDatum(getString(code, result, 9));
+                        crs = ServicesForMetadata.createParametricCRS(createProperties("Coordinate Reference System",
+                                name, epsg, area, scope, remarks, deprecated), datum, cs, crsFactory);
                         break;
                     }
                     /* ----------------------------------------------------------------------
@@ -1761,7 +1765,7 @@
                         break;
                     }
                     case "parametric": {
-                        datum = datumFactory.createParametricDatum(properties);
+                        datum = ServicesForMetadata.createParametricDatum(properties, datumFactory);
                         break;
                     }
                     default: {
@@ -2219,7 +2223,7 @@
                     }
                     case WKTKeywords.parametric: {
                         switch (dimension) {
-                            case 1: cs = csFactory.createParametricCS(properties, axes[0]); break;
+                            case 1: cs = ServicesForMetadata.createParametricCS(properties, axes[0], csFactory); break;
                         }
                         break;
                     }
@@ -2642,7 +2646,7 @@
                 }
                 final Map<String, Object> properties =
                         createProperties("Coordinate_Operation Parameter", name, epsg, isReversible, deprecated);
-                properties.put(Identifier.DESCRIPTION_KEY, description);
+                properties.put(ImmutableIdentifier.DESCRIPTION_KEY, description);
                 final ParameterDescriptor<?> descriptor = new DefaultParameterDescriptor<>(properties,
                         1, 1, type, valueDomain, null, null);
                 returnValue = ensureSingleton(descriptor, returnValue, code);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
index 4dff311..b3df2bc 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
@@ -25,6 +25,11 @@
 import org.apache.sis.internal.referencing.WKTKeywords;
 import org.apache.sis.util.CharSequences;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.crs.DefaultParametricCRS;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+
 
 /**
  * Information about a specific table. The MS-Access dialect of SQL is assumed;
@@ -72,7 +77,7 @@
                 "COORD_REF_SYS_KIND",
                 new Class<?>[] { ProjectedCRS.class,   GeographicCRS.class,   GeocentricCRS.class,
                                  VerticalCRS.class,    CompoundCRS.class,     EngineeringCRS.class,
-                                 DerivedCRS.class,     TemporalCRS.class,     ParametricCRS.class},     // See comment below
+                                 DerivedCRS.class,     TemporalCRS.class,     DefaultParametricCRS.class},     // See comment below
                 new String[]   {"projected",          "geographic",          "geocentric",
                                 "vertical",           "compound",            "engineering",
                                 "derived",            "temporal",            "parametric"},             // See comment below
@@ -91,7 +96,7 @@
                 "COORD_SYS_TYPE",
                 new Class<?>[] {CartesianCS.class,      EllipsoidalCS.class,      VerticalCS.class,      LinearCS.class,
                                 SphericalCS.class,      PolarCS.class,            CylindricalCS.class,
-                                TimeCS.class,           ParametricCS.class,       AffineCS.class},
+                                TimeCS.class,           DefaultParametricCS.class, AffineCS.class},
                 new String[]   {WKTKeywords.Cartesian,  WKTKeywords.ellipsoidal,  WKTKeywords.vertical,  WKTKeywords.linear,
                                 WKTKeywords.spherical,  WKTKeywords.polar,        WKTKeywords.cylindrical,
                                 WKTKeywords.temporal,   WKTKeywords.parametric,   WKTKeywords.affine},      // Same comment than in the CRS case above.
@@ -110,7 +115,7 @@
                 "DATUM_NAME",
                 "DATUM_TYPE",
                 new Class<?>[] { GeodeticDatum.class,  VerticalDatum.class,   EngineeringDatum.class,
-                                 TemporalDatum.class,  ParametricDatum.class},
+                                 TemporalDatum.class,  DefaultParametricDatum.class},
                 new String[]   {"geodetic",           "vertical",            "engineering",
                                 "temporal",           "parametric"},         // Same comment than in the CRS case above.
                 null),
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index 51ea821..1fb1361 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -945,7 +945,7 @@
                     method = DefaultOperationMethod.redimension(method, sourceDimensions, targetDimensions);
                 } catch (IllegalArgumentException ex) {
                     try {
-                        method = factory.getOperationMethod(method.getName().getCode());
+                        method = factorySIS.getOperationMethod(method.getName().getCode());
                         method = DefaultOperationMethod.redimension(method, sourceDimensions, targetDimensions);
                     } catch (NoSuchIdentifierException | IllegalArgumentException se) {
                         ex.addSuppressed(se);
@@ -1293,10 +1293,10 @@
                 if (descriptor != null) {
                     final Identifier name = descriptor.getName();
                     if (name != null) {
-                        method = factory.getOperationMethod(name.getCode());
+                        method = factorySIS.getOperationMethod(name.getCode());
                     }
                     if (method == null) {
-                        method = factory.createOperationMethod(properties,
+                        method = factorySIS.createOperationMethod(properties,
                                 sourceCRS.getCoordinateSystem().getDimension(),
                                 targetCRS.getCoordinateSystem().getDimension(),
                                 descriptor);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
index 833d3f4..e7a566d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
@@ -279,7 +279,6 @@
      *
      * @see DefaultMathTransformFactory#getOperationMethod(String)
      */
-    @Override
     public OperationMethod getOperationMethod(String name) throws FactoryException {
         name = CharSequences.trimWhitespaces(name);
         ArgumentChecks.ensureNonEmpty("name", name);
@@ -343,7 +342,6 @@
      *
      * @see DefaultOperationMethod#DefaultOperationMethod(Map, Integer, Integer, ParameterDescriptorGroup)
      */
-    @Override
     public OperationMethod createOperationMethod(final Map<String,?> properties,
             final Integer sourceDimensions, final Integer targetDimensions,
             ParameterDescriptorGroup parameters) throws FactoryException
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MismatchedDatumException.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MismatchedDatumException.java
index bb46bf9..23c2441 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MismatchedDatumException.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MismatchedDatumException.java
@@ -36,7 +36,6 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.6
  *
- * @see org.opengis.geometry.MismatchedReferenceSystemException
  * @see org.opengis.geometry.MismatchedDimensionException
  * @see org.apache.sis.referencing.operation.matrix.MismatchedMatrixSizeException
  *
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubOperationInfo.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubOperationInfo.java
index 0e3185c..acbf470 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubOperationInfo.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubOperationInfo.java
@@ -25,6 +25,9 @@
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.crs.DefaultParametricCRS;
+
 
 /**
  * Information about the operation from a source component to a target component in {@code CompoundCRS} instances.
@@ -58,7 +61,7 @@
         {GeodeticCRS.class},
         {VerticalCRS.class, GeodeticCRS.class},
         {TemporalCRS.class},
-        {ParametricCRS.class},
+        {DefaultParametricCRS.class},
         {EngineeringCRS.class},
         {ImageCRS.class}
     };
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
index 487f2cd..9c3177e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
@@ -322,7 +322,7 @@
      * </ul>
      *
      * This method ignores the {@linkplain Envelope#getCoordinateReferenceSystem() envelope CRS}, which may be null.
-     * Actually this method is used more often for {@linkplain org.opengis.coverage.grid.GridEnvelope grid envelopes}
+     * Actually this method is used more often for grid envelopes
      * (which have no CRS) than geodetic envelopes.
      *
      * <h4>Crossing the anti-meridian of a Geographic CRS</h4>
@@ -463,7 +463,7 @@
      * </ul>
      *
      * This method ignores the {@linkplain Envelope#getCoordinateReferenceSystem() envelope CRS}, which may be null.
-     * Actually this method is used more often for {@linkplain org.opengis.coverage.grid.GridEnvelope grid envelopes}
+     * Actually this method is used more often for grid envelopes
      * (which have no CRS) than geodetic envelopes.
      *
      * <h4>Crossing the anti-meridian of a Geographic CRS</h4>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java
index f7cdf26..610e3f4 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java
@@ -30,7 +30,6 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.4
  *
- * @see org.opengis.geometry.MismatchedReferenceSystemException
  * @see org.apache.sis.referencing.operation.MismatchedDatumException
  *
  * @since 0.4
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
index 07946d5..fd2bb37 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
@@ -24,7 +24,6 @@
 import java.util.regex.Pattern;
 import java.io.Serializable;
 import java.lang.reflect.Modifier;
-import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
@@ -64,6 +63,9 @@
 import static java.lang.Math.*;
 import static java.util.logging.Logger.getLogger;
 
+// Branch-dependent imports
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Base class for conversion services between ellipsoidal and cartographic projections.
@@ -468,7 +470,7 @@
         for (final V variant : variants) {
             final String identifier = variant.getIdentifier();
             if (identifier != null) {
-                for (final Identifier id : method.getIdentifiers()) {
+                for (final ReferenceIdentifier id : method.getIdentifiers()) {
                     if (Constants.EPSG.equals(id.getCodeSpace()) && identifier.equals(id.getCode())) {
                         return variant;
                     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
index 6766f5d..1ea0400 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
@@ -1582,7 +1582,7 @@
 
     /**
      * Creates a math transform object from a
-     * <a href="http://www.geoapi.org/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
+     * <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
      * Known Text</cite> (WKT)</a>.
      * If the given text contains non-fatal anomalies (unknown or unsupported WKT elements,
      * inconsistent unit definitions, <i>etc.</i>), warnings may be reported in a
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
index 73e125f..92b3765 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
@@ -278,8 +278,6 @@
      * @param  values    the output values (<var>y</var>) in the function range, or {@code null}.
      * @return the <i>y=f(x)</i> function.
      *
-     * @see org.opengis.coverage.InterpolationMethod
-     *
      * @since 0.7
      */
     public static MathTransform1D interpolate(final double[] preimage, final double[] values) {
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/geometry/AbstractEnvelopeTest.java b/core/sis-referencing/src/test/java/org/apache/sis/geometry/AbstractEnvelopeTest.java
index 1a99906..1a45d46 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/geometry/AbstractEnvelopeTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/geometry/AbstractEnvelopeTest.java
@@ -87,7 +87,7 @@
             }
             default: throw new IllegalArgumentException(String.valueOf(type));
         }
-        if (type != RECTANGLE) {
+        if (PENDING_NEXT_GEOAPI_RELEASE) {
             validate(envelope);
         }
         return envelope;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java b/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
index ac572c7..15c4130 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
@@ -59,7 +59,7 @@
      * This is set to {@code true} only when we intentionally want to create an invalid envelope,
      * for example in order to test normalization.
      */
-    boolean skipValidation;
+    boolean skipValidation = !PENDING_NEXT_GEOAPI_RELEASE;
 
     /**
      * Creates a new geographic envelope for the given coordinate values.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameterGroupTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameterGroupTest.java
index cec3e88..bbec651 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameterGroupTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameterGroupTest.java
@@ -198,7 +198,6 @@
                                              final ParameterDescriptor<?> actual)
     {
         assertEquals("name",          expected.getName(),         actual.getName());
-        assertEquals("description",   expected.getDescription(),  actual.getDescription());
         assertEquals("valueClass",    expected.getValueClass(),   actual.getValueClass());
         assertEquals("validValues",   expected.getValidValues(),  actual.getValidValues());
         assertEquals("unit",          expected.getUnit(),         actual.getUnit());
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CodeTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CodeTest.java
index fc998b3..e6f7e8b 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CodeTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CodeTest.java
@@ -18,7 +18,7 @@
 
 import java.util.Collections;
 import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.simple.SimpleCitation;
 import org.apache.sis.referencing.ImmutableIdentifier;
@@ -41,15 +41,15 @@
  */
 public final strictfp class CodeTest extends TestCase {
     /**
-     * Tests the {@link Code#Code(Identifier)} constructor with {@code "EPSG:4326"} identifier.
+     * Tests the {@link Code#Code(ReferenceIdentifier)} constructor with {@code "EPSG:4326"} identifier.
      * This test intentionally uses an identifier with the {@code IOGP} authority instead of
      * EPSG in order to make sure that the {@code codeSpace} attribute is set from
-     * {@link Identifier#getCodeSpace()}, not from {@link Identifier#getAuthority()}.
+     * {@code Identifier.getCodeSpace()}, not from {@code Identifier.getAuthority()}.
      */
     @Test
     public void testSimple() {
         final SimpleCitation IOGP = new SimpleCitation("IOGP");
-        final Identifier id = new ImmutableIdentifier(IOGP, "EPSG", "4326");  // See above javadoc.
+        final ReferenceIdentifier id = new ImmutableIdentifier(IOGP, "EPSG", "4326");  // See above javadoc.
         final Code value = new Code(id);
         assertEquals("codeSpace", "EPSG", value.codeSpace);
         assertEquals("code",      "4326", value.code);
@@ -57,7 +57,7 @@
          * Reverse operation. Note that the authority is lost since there is no room for that in a
          * <gml:identifier> element. Current implementation sets the authority to the code space.
          */
-        final Identifier actual = value.getIdentifier();
+        final ReferenceIdentifier actual = value.getIdentifier();
         assertSame  ("authority",  Citations.EPSG, actual.getAuthority());
         assertEquals("codeSpace", "EPSG", actual.getCodeSpace());
         assertNull  ("version",           actual.getVersion());
@@ -65,7 +65,7 @@
     }
 
     /**
-     * Tests the {@link Code#Code(Identifier)} constructor with {@code "EPSG:8.3:4326"} identifier.
+     * Tests the {@link Code#Code(ReferenceIdentifier)} constructor with {@code "EPSG:8.3:4326"} identifier.
      * This test intentionally uses an identifier with the {@code IOGP} authority instead of EPSG
      * for the same reason than {@link #testSimple()}.
      */
@@ -73,7 +73,7 @@
     @DependsOnMethod("testSimple")
     public void testWithVersion() {
         final SimpleCitation IOGP = new SimpleCitation("IOGP");
-        final Identifier id = new ImmutableIdentifier(IOGP, "EPSG", "4326", "8.2", null);  // See above javadoc.
+        final ReferenceIdentifier id = new ImmutableIdentifier(IOGP, "EPSG", "4326", "8.2", null);  // See above javadoc.
         final Code value = new Code(id);
         assertEquals("codeSpace", "EPSG:8.2", value.codeSpace);
         assertEquals("code",      "4326",     value.code);
@@ -81,7 +81,7 @@
          * Reverse operation. Note that the authority is lost since there is no room for that in a
          * <gml:identifier> element. Current implementation sets the authority to the code space.
          */
-        final Identifier actual = value.getIdentifier();
+        final ReferenceIdentifier actual = value.getIdentifier();
         assertSame  ("authority",  Citations.EPSG, actual.getAuthority());
         assertEquals("codeSpace", "EPSG", actual.getCodeSpace());
         assertEquals("version",   "8.2",  actual.getVersion());
@@ -94,7 +94,7 @@
     @Test
     @DependsOnMethod("testWithVersion")
     public void testForIdentifiedObject() {
-        final Identifier id = new ImmutableIdentifier(Citations.EPSG, "EPSG", "4326", "8.2", null);
+        final ReferenceIdentifier id = new ImmutableIdentifier(Citations.EPSG, "EPSG", "4326", "8.2", null);
         final Code value = Code.forIdentifiedObject(GeographicCRS.class, Collections.singleton(id));
         assertNotNull(value);
         assertEquals("codeSpace", Constants.IOGP, value.codeSpace);
@@ -111,7 +111,7 @@
         final DefaultCitation authority = new DefaultCitation("EPSG");
         authority.getIdentifiers().add(new ImmutableIdentifier(null, "OGP", "EPSG"));
 
-        final Identifier id = new ImmutableIdentifier(authority, "EPSG", "4326", "8.2", null);
+        final ReferenceIdentifier id = new ImmutableIdentifier(authority, "EPSG", "4326", "8.2", null);
         final Code value = Code.forIdentifiedObject(GeographicCRS.class, Collections.singleton(id));
         assertNotNull(value);
         assertEquals("codeSpace", "OGP", value.codeSpace);
@@ -129,7 +129,7 @@
         final Code value = new Code();
         value.codeSpace = "OGP";
         value.code = "urn:ogc:def:crs:EPSG:8.2:4326";
-        final Identifier actual = value.getIdentifier();
+        final ReferenceIdentifier actual = value.getIdentifier();
         assertSame  ("authority",  Citations.EPSG, actual.getAuthority());
         assertEquals("codeSpace", "EPSG", actual.getCodeSpace());
         assertEquals("version",   "8.2",  actual.getVersion());
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
index 27ed04d..83d3f0c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
@@ -119,7 +119,7 @@
         assertEquals("cylindricalCS",    toPropertyName(CoordinateSystem.class, CylindricalCS   .class).toString());
         assertEquals("ellipsoidalCS",    toPropertyName(CoordinateSystem.class, EllipsoidalCS   .class).toString());
         assertEquals("linearCS",         toPropertyName(CoordinateSystem.class, LinearCS        .class).toString());
-        assertEquals("parametricCS",     toPropertyName(CoordinateSystem.class, ParametricCS    .class).toString());
+//      assertEquals("parametricCS",     toPropertyName(CoordinateSystem.class, ParametricCS    .class).toString());
         assertEquals("polarCS",          toPropertyName(CoordinateSystem.class, PolarCS         .class).toString());
         assertEquals("sphericalCS",      toPropertyName(CoordinateSystem.class, SphericalCS     .class).toString());
         assertEquals("timeCS",           toPropertyName(CoordinateSystem.class, TimeCS          .class).toString());
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
index 385e3e4..56447a4 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
@@ -51,7 +51,7 @@
         assertEquals(WKTKeywords.cylindrical, toType(CoordinateSystem.class, CylindricalCS   .class));
         assertEquals(WKTKeywords.ellipsoidal, toType(CoordinateSystem.class, EllipsoidalCS   .class));
         assertEquals(WKTKeywords.linear,      toType(CoordinateSystem.class, LinearCS        .class));
-        assertEquals(WKTKeywords.parametric,  toType(CoordinateSystem.class, ParametricCS    .class));
+//      assertEquals(WKTKeywords.parametric,  toType(CoordinateSystem.class, ParametricCS    .class));
         assertEquals(WKTKeywords.polar,       toType(CoordinateSystem.class, PolarCS         .class));
         assertEquals(WKTKeywords.spherical,   toType(CoordinateSystem.class, SphericalCS     .class));
         assertEquals(WKTKeywords.temporal,    toType(CoordinateSystem.class, TimeCS          .class));
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/CoordinateFrameRotationTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/CoordinateFrameRotationTest.java
index d24fd55..7e7d548 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/CoordinateFrameRotationTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/CoordinateFrameRotationTest.java
@@ -82,6 +82,7 @@
         tolerance  = Formulas.ANGULAR_TOLERANCE;
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
+        tolerance  = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         createTransform(new CoordinateFrameRotation3D());
         assertFalse(transform instanceof LinearTransform);
         verifyTransform(PositionVector7ParamTest.samplePoint(1),
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java
index 8f69416..895c263 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java
@@ -39,10 +39,6 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.7
- *
- * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()
- * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()
- *
  * @since 0.7
  * @module
  */
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java
index a9e0c49..840f677 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.internal.referencing.provider;
 
-import java.util.Arrays;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.datum.Ellipsoid;
@@ -24,12 +23,10 @@
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.test.ToleranceModifier;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.referencing.datum.HardCodedDatum;
 import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.referencing.operation.transform.CoordinateDomain;
 import org.apache.sis.referencing.operation.transform.EllipsoidToCentricTransform;
@@ -230,6 +227,7 @@
      */
     private void datumShift(final int sourceStep, final int targetStep) throws TransformException {
         tolerance = precision(targetStep);
+        tolerance = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         verifyTransform(samplePoint(sourceStep), samplePoint(targetStep));
         validate();
     }
@@ -263,43 +261,11 @@
         derivativeDeltas = new double[] {delta, delta, 100};    // (Δλ, Δφ, Δh)
         zTolerance = Formulas.LINEAR_TOLERANCE / 2;             // Half the precision of h value given by EPSG
         zDimension = new int[] {2};                             // Dimension of h where to apply zTolerance
+        tolerance  = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         datumShift(1, 4);
     }
 
     /**
-     * Tests the point used in {@link FranceGeocentricInterpolationTest}. We use this test for making sure
-     * that we get the expected value when using the real geocentric translation method. This test will be
-     * completed by an equivalent test in {@code MolodenskyTransformTest} for verifying the quality of our
-     * approximation.
-     *
-     * @throws FactoryException if an error occurred while creating the transform.
-     * @throws TransformException if transformation of a point failed.
-     *
-     * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()
-     */
-    @Test
-    public void testFranceGeocentricInterpolationPoint() throws FactoryException, TransformException {
-        transform = createDatumShiftForGeographic3D(DefaultFactories.forBuildin(MathTransformFactory.class),
-                 HardCodedDatum.NTF.getEllipsoid(),
-                 CommonCRS.ETRS89.ellipsoid(),
-                -FranceGeocentricInterpolation.TX,
-                -FranceGeocentricInterpolation.TY,
-                -FranceGeocentricInterpolation.TZ);
-
-        final double delta = toRadians(100.0 / 60) / 1852;                  // Approximately 100 metres
-        derivativeDeltas = new double[] {delta, delta, 100};                // (Δλ, Δφ, Δh)
-        tolerance  = FranceGeocentricInterpolationTest.ANGULAR_TOLERANCE;
-        zTolerance = 0.005;                       // Half the precision of the target[2] value set below.
-        zDimension = new int[] {2};
-
-        final double[] source   = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(1), 3);
-        final double[] expected = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(2), 3);
-        expected[2] = 43.15;  // Anti-regression (this value is not provided in NTG_88 guidance note).
-        verifyTransform(source, expected);
-        validate();
-    }
-
-    /**
      * Tests conversion of random points.
      *
      * @throws FactoryException if an error occurred while creating the transform.
@@ -310,7 +276,7 @@
     public void testRandomPoints() throws FactoryException, TransformException {
         testGeographicDomain();                     // For creating the transform.
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.GEOGRAPHIC;
+//      toleranceModifier = ToleranceModifier.GEOGRAPHIC;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 831342815);
     }
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java
index 76ed840..60c52b6 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java
@@ -27,7 +27,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java
index 2d3ad76..24df893 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java
@@ -18,7 +18,7 @@
 
 import java.util.Iterator;
 import org.opengis.util.GenericName;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.apache.sis.metadata.iso.citation.Citations;
@@ -127,8 +127,8 @@
         assertEquals("minimumOccurs", isMandatory ? 1 : 0, actual.getMinimumOccurs());
         if (epsgName != null) {
             for (final GenericName alias : actual.getAlias()) {
-                if (alias instanceof Identifier && ((Identifier) alias).getAuthority() != Citations.EPSG) {
-                    assertOgcIdentifierEquals(ogcName, (Identifier) alias);
+                if (alias instanceof ReferenceIdentifier && ((ReferenceIdentifier) alias).getAuthority() != Citations.EPSG) {
+                    assertOgcIdentifierEquals(ogcName, (ReferenceIdentifier) alias);
                     return;
                 }
             }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java
index aeca2ac..a9b34d2 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java
@@ -32,7 +32,7 @@
 import org.apache.sis.measure.Units;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java
index 2a16de9..53f6954 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java
@@ -40,7 +40,7 @@
 import org.junit.Test;
 
 import static org.junit.Assume.assumeTrue;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 import static org.apache.sis.internal.referencing.provider.DatumShiftGridLoader.DEGREES_TO_SECONDS;
 
 
@@ -51,7 +51,6 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
- * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()
  * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()
  *
  * @since 0.7
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ParameterNameTableGenerator.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ParameterNameTableGenerator.java
deleted file mode 100644
index 5bb2788..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ParameterNameTableGenerator.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * 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.referencing.provider;
-
-import java.util.List;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-import java.lang.reflect.Field;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Files;
-import java.nio.file.FileVisitResult;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import javax.measure.Unit;
-import org.opengis.util.GenericName;
-import org.opengis.metadata.Identifier;
-import org.apache.sis.referencing.NamedIdentifier;
-import org.apache.sis.parameter.DefaultParameterDescriptor;
-import org.apache.sis.test.ProjectDirectories;
-import org.apache.sis.internal.jdk9.JDK9;
-import org.apache.sis.measure.Angle;
-import org.apache.sis.measure.Latitude;
-import org.apache.sis.measure.Longitude;
-import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.StringBuilders;
-import org.apache.sis.measure.Range;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Inserts comments with parameter names in the javadoc of parameters.
- * This class needs to be run explicitly; it is not part of JUnit tests.
- * After execution, files in the provider packages may be overwritten.
- * Developer should execute {@code "git diff"} and inspect the changes.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final class ParameterNameTableGenerator extends SimpleFileVisitor<Path> {
-    /**
-     * Value as the kind of object expected in {@link DefaultParameterDescriptor}.
-     */
-    private static final Double ONE           =  1d,
-                                POSITIVE_ZERO = +0d,
-                                NEGATIVE_ZERO = -0d,
-                                MIN_LONGITUDE = Longitude.MIN_VALUE,
-                                MAX_LONGITUDE = Longitude.MAX_VALUE,
-                                MIN_LATITUDE  =  Latitude.MIN_VALUE,
-                                MAX_LATITUDE  =  Latitude.MAX_VALUE;
-
-    /**
-     * The directory of Java source code to scan.
-     */
-    private final Path directory;
-
-    /**
-     * Pattern of the lines to search.
-     */
-    private final Pattern toSearch;
-
-    /**
-     * A temporary buffer for creating lines of comment.
-     */
-    private final StringBuilder buffer;
-
-    /**
-     * All lines in the file being processed.
-     */
-    private List<String> lines;
-
-    /**
-     * For {@link #main(String[])} only.
-     */
-    private ParameterNameTableGenerator() {
-        directory = new ProjectDirectories(getClass()).getSourcesPackageDirectory("core/sis-referencing");
-        toSearch  = Pattern.compile(".*\\s+static\\s+.*ParameterDescriptor<\\w+>\\s*(\\w+)\\s*[=;].*");
-        buffer    = new StringBuilder();
-    }
-
-    /**
-     * Launches the insertion of comment lines.
-     *
-     * @param  args  ignored.
-     * @throws IOException if an error occurred while reading or writing a file.
-     */
-    public static void main(final String[] args) throws IOException {
-        final ParameterNameTableGenerator cg = new ParameterNameTableGenerator();
-        Files.walkFileTree(cg.directory, cg);
-    }
-
-    /**
-     * Invoked before to enter in a sub-directory. This implementation skips all sub-directories.
-     *
-     * @param  dir    the directory in which to enter.
-     * @param  attrs  ignored.
-     * @return flag instructing whether to scan that directory or not.
-     */
-    @Override
-    public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) {
-        return dir.equals(directory) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
-    }
-
-    /**
-     * Invoked for each file in the scanned directory. If the file is a Java file,
-     * searches for {@code ParameterDescriptor} declarations. Otherwise ignore.
-     *
-     * @param  file   the file.
-     * @param  attrs  ignored.
-     * @return {@link FileVisitResult#CONTINUE}.
-     * @throws IOException if an error occurred while reading or writing the given file.
-     */
-    @Override
-    public FileVisitResult visitFile​(final Path file, final BasicFileAttributes attrs) throws IOException {
-        final String name = file.getFileName().toString();
-        if (name.endsWith(".java")) {
-            addCommentsToJavaFile(file);
-        }
-        return super.visitFile(file, attrs);
-    }
-
-    /**
-     * Returns the class for the given Java source file.
-     */
-    private Class<?> getClass(final Path file) throws ClassNotFoundException {
-        String name = file.getFileName().toString();
-        name = name.substring(0, name.lastIndexOf('.'));    // Remove the ".java" suffix.
-        name = JDK9.getPackageName(getClass()) + '.' + name;
-        return Class.forName(name);
-    }
-
-    /**
-     * Finds the parameter descriptors in the given file, and add parameter names in comments.
-     */
-    private void addCommentsToJavaFile(final Path file) throws IOException {
-        Class<?> classe = null;
-        final Matcher matcher = toSearch.matcher("");
-        lines = Files.readAllLines(file);
-        for (int i=lines.size(); --i >= 0;) {
-            final String line = lines.get(i);
-            if (matcher.reset(line).matches()) {
-                final String fieldName = matcher.group(1);
-                final DefaultParameterDescriptor<?> descriptor;
-                try {
-                    if (classe == null) {
-                        classe = getClass(file);
-                    }
-                    final Field field = classe.getDeclaredField(fieldName);
-                    field.setAccessible(true);
-                    descriptor = (DefaultParameterDescriptor<?>) field.get(null);
-                } catch (ReflectiveOperationException e) {
-                    throw new AssertionError(e);
-                }
-                /*
-                 * Find the line where to insert comments. We detect if the comment to insert already exists
-                 * by looking for this class name (expected in a HTML comment) before the start of comments.
-                 * If we find that line, then we delete everything between it and the end of comments before
-                 * to regenerate them. Developer can execute "git diff" on the command line for checking if
-                 * any lines changed as a result.
-                 */
-                int insertAt = i;
-                String previous;
-                do {
-                    previous = lines.get(--insertAt).trim();
-                    if (!previous.startsWith("*") && !previous.startsWith("@")) {
-                        fail("Unexpected content in " + file.getFileName() + " at line " + insertAt);
-                    }
-                } while (!previous.equals("*/"));
-                previous = lines.get(insertAt);
-                buffer.setLength(0);
-                buffer.append(previous, 0, previous.indexOf('*') + 1);
-                for (int check = insertAt;;) {
-                    previous = lines.get(--check).trim();
-                    if (previous.equals("/**")) break;
-                    if (previous.contains("ParameterNameTableGenerator")) {
-                        lines.subList(check, insertAt).clear();
-                        insertAt = check;
-                        break;
-                    }
-                }
-                /*
-                 * Format a HTML table in the comment with the name and aliases of each parameter.
-                 */
-                write(insertAt++, "<!-- Generated by ParameterNameTableGenerator -->");
-                write(insertAt++, "<table class=\"sis\">");
-                write(insertAt++, "  <caption>Parameter names</caption>");
-                write(insertAt++, descriptor.getName());
-                for (final GenericName alias : descriptor.getAlias()) {
-                    write(insertAt++, (alias instanceof Identifier) ? (Identifier) alias : new NamedIdentifier(alias));
-                }
-                write(insertAt++, "</table>");
-                /*
-                 * Format other information: default value, value domain, whether the value is mandatory, etc.
-                 * Default value of zero are omitted (i.e. unless otherwise specified, default values are zero
-                 * in the tables that we format). Range of values of [-90 … 90]° for latitude or [-180 … 180]°
-                 * for longitude are also omitted. In other words, we report only "unusual" things in the notes.
-                 */
-                Object  defaultValue = descriptor.getDefaultValue();
-                Range<?> valueDomain = descriptor.getValueDomain();
-                boolean  isOptional  = descriptor.getMinimumOccurs() == 0;
-                boolean  noDefault   = (defaultValue == null);
-                Object   minValue    = null;
-                Object   maxValue    = null;
-                if (valueDomain != null) {
-                    minValue = valueDomain.getMinValue();
-                    maxValue = valueDomain.getMaxValue();
-                    final boolean inclusive = valueDomain.isMinIncluded() && valueDomain.isMaxIncluded();
-                    if (fieldName.contains("LATITUDE") || fieldName.contains("PARALLEL")) {
-                        if (inclusive && MIN_LATITUDE.equals(minValue) && MAX_LATITUDE.equals(maxValue)) {
-                            valueDomain = null;
-                        }
-                    } else if (fieldName.contains("LONGITUDE") || fieldName.contains("MERIDIAN")) {
-                        if (inclusive && MIN_LONGITUDE.equals(minValue) && MAX_LONGITUDE.equals(maxValue)) {
-                            valueDomain = null;
-                        }
-                    } else if (fieldName.contains("SCALE")) {
-                        if (!inclusive && POSITIVE_ZERO.equals(minValue) && maxValue == null) {
-                            valueDomain = null;
-                        }
-                    } else if (minValue == null && maxValue == null) {
-                        valueDomain = null;
-                    }
-                }
-                if ((fieldName.contains("SCALE") ? ONE : POSITIVE_ZERO).equals(defaultValue)) {
-                    defaultValue = null;
-                }
-                if (defaultValue != null || valueDomain != null || isOptional || noDefault) {
-                    write(insertAt++, "<b>Notes:</b>");
-                    write(insertAt++, "<ul>");
-                    if (valueDomain != null) {
-                        final int p = buffer.length();
-                        buffer.append("   <li>");
-                        if ((minValue != null && minValue.equals(maxValue)) ||
-                            (NEGATIVE_ZERO.equals(minValue) && POSITIVE_ZERO.equals(maxValue)))
-                        {
-                            buffer.append("Value restricted to ").append(maxValue);
-                            StringBuilders.trimFractionalPart(buffer);
-                        } else {
-                            buffer.append("Value domain: ").append(valueDomain);
-                        }
-                        lines.add(insertAt++, buffer.append("</li>").toString());
-                        buffer.setLength(p);
-                    }
-                    if (defaultValue != null) {
-                        final int p = buffer.length();
-                        final boolean isText = !(defaultValue instanceof Number || defaultValue instanceof Angle);
-                        buffer.append("   <li>").append("Default value: ");
-                        if (isText) buffer.append("{@code ");
-                        buffer.append(defaultValue);
-                        if (isText) buffer.append('}');
-                        StringBuilders.trimFractionalPart(buffer);
-                        final Unit<?> unit = descriptor.getUnit();
-                        if (unit != null) {
-                            final String symbol = unit.getSymbol();
-                            if (!symbol.isEmpty()) {
-                                if (Character.isLetterOrDigit(symbol.charAt(0))) {
-                                    buffer.append(' ');
-                                }
-                                buffer.append(symbol);
-                            }
-                        }
-                        lines.add(insertAt++, buffer.append("</li>").toString());
-                        buffer.setLength(p);
-                    } else if (noDefault) {
-                        write(insertAt++, "  <li>No default value</li>");
-                    }
-                    if (isOptional) {
-                        write(insertAt++, "  <li>Optional</li>");
-                    }
-                    write(insertAt++, "</ul>");
-                }
-            }
-        }
-        /*
-         * If at least one table has been formatted, rewrite the file.
-         */
-        if (classe != null) {
-            Files.write(file, lines);
-        }
-        lines = null;
-    }
-
-    /**
-     * Appends the given line at the given position. This method writes the margin
-     * (typically spaces followed by {@code '*'} and a single space) before the line.
-     */
-    private void write(final int insertAt, final String line) {
-        final int p = buffer.length();
-        if (!line.isEmpty()) {
-            buffer.append(' ').append(line);
-        }
-        lines.add(insertAt, buffer.toString());
-        buffer.setLength(p);
-    }
-
-    /**
-     * Appends the given authority and name at the given position. This method writes the margin
-     * (typically spaces followed by {@code '*'} and a single space) before the line.
-     */
-    private void write(final int insertAt, final Identifier id) {
-        final int p = buffer.length();
-        final String authority = id.getCodeSpace();
-        buffer.append("   <tr><td> ").append(authority).append(':')
-              .append(CharSequences.spaces(8 - authority.length()))
-              .append("</td><td> ").append(id.getCode()).append(" </td></tr>");
-        lines.add(insertAt, buffer.toString());
-        buffer.setLength(p);
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PositionVector7ParamTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PositionVector7ParamTest.java
index 7a1ee4b..43a818c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PositionVector7ParamTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PositionVector7ParamTest.java
@@ -138,6 +138,7 @@
         tolerance  = Formulas.ANGULAR_TOLERANCE;
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
+        tolerance  = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         createTransform(new PositionVector7Param3D());
         assertFalse(transform instanceof LinearTransform);
         verifyTransform(samplePoint(1), samplePoint(4));
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProviderMock.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProviderMock.java
index ea85948..448bc8d 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProviderMock.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProviderMock.java
@@ -29,7 +29,7 @@
 /**
  * Base class of mock provider for coordinate operations not yet implemented in Apache SIS.
  * This is used for operations needed for executing some Well Known Text (WKT) parsing tests
- * in the {@link org.apache.sis.io.wkt.WKTParserTest} class, without doing any real coordinate
+ * in the {@code org.apache.sis.io.wkt.WKTParserTest} class, without doing any real coordinate
  * operations with the parsed objects.
  *
  * <p>Subclasses may be promoted to a real operation if we implement their formulas in a future Apache SIS version.</p>
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
index 12e85a3..8cf5f89 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
@@ -25,6 +25,7 @@
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.referencing.operation.OperationMethod;
 import org.apache.sis.referencing.operation.DefaultOperationMethod;
+import org.apache.sis.parameter.DefaultParameterDescriptor;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
@@ -270,8 +271,8 @@
      */
     @Test
     public void testDescription() {
-        assertFalse(SatelliteTracking.SATELLITE_ORBIT_INCLINATION.getDescription().length() == 0);
-        assertFalse(SatelliteTracking.SATELLITE_ORBITAL_PERIOD   .getDescription().length() == 0);
-        assertFalse(SatelliteTracking.ASCENDING_NODE_PERIOD      .getDescription().length() == 0);
+        assertFalse(((DefaultParameterDescriptor<Double>) SatelliteTracking.SATELLITE_ORBIT_INCLINATION).getDescription().length() == 0);
+        assertFalse(((DefaultParameterDescriptor<Double>) SatelliteTracking.SATELLITE_ORBITAL_PERIOD   ).getDescription().length() == 0);
+        assertFalse(((DefaultParameterDescriptor<Double>) SatelliteTracking.ASCENDING_NODE_PERIOD      ).getDescription().length() == 0);
     }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/SeismicBinGridMock.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/SeismicBinGridMock.java
index abe4383..7f625ad 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/SeismicBinGridMock.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/SeismicBinGridMock.java
@@ -26,7 +26,7 @@
  * The provider for <cite>"P6 (I = J-90°) seismic bin grid transformation"</cite> transformation (EPSG:1049).
  *
  * This conversion is not yet implemented in Apache SIS, but we need to at least accept the parameters
- * for a Well Known Text (WKT) parsing test in the {@link org.apache.sis.io.wkt.WKTParserTest} class.
+ * for a Well Known Text (WKT) parsing test in the {@code org.apache.sis.io.wkt.WKTParserTest} class.
  *
  * <p>This class may be promoted to a real operation if we implement the formulas in a future Apache SIS version.</p>
  *
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ComparisonWithEPSG.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ComparisonWithEPSG.java
index 228946d..1d54fa3 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ComparisonWithEPSG.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ComparisonWithEPSG.java
@@ -23,7 +23,6 @@
 import org.apache.sis.referencing.factory.TestFactorySource;
 import org.apache.sis.referencing.factory.sql.EPSGFactory;
 import org.apache.sis.referencing.CRS;
-import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.BeforeClass;
 import org.junit.AfterClass;
@@ -41,7 +40,6 @@
  * @since   1.0
  * @module
  */
-@DependsOn(WKTParserTest.class)
 public final strictfp class ComparisonWithEPSG extends TestCase {
     /**
      * Creates the factory to use for all tests in this class.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java
index f70e0e8..fef0a4e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java
@@ -110,7 +110,7 @@
     }
 
     /**
-     * Tests (indirectly) {@link Formatter#append(ControlledVocabulary)}.
+     * Tests (indirectly) {@code Formatter.append(ControlledVocabulary)}.
      */
     @Test
     public void testAppendCodeList() {
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
index 78ca272..39cfa41 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
@@ -71,8 +71,7 @@
     org.apache.sis.referencing.crs.DefaultCompoundCRSTest.class,
     org.apache.sis.referencing.crs.DefaultEngineeringCRSTest.class,
     org.apache.sis.referencing.crs.DefaultImageCRSTest.class,
-    org.apache.sis.referencing.cs.DirectionAlongMeridianTest.class,
-    org.apache.sis.referencing.factory.GeodeticObjectFactoryTest.class
+    org.apache.sis.referencing.cs.DirectionAlongMeridianTest.class
 })
 public final strictfp class GeodeticObjectParserTest extends TestCase {
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java
index baef439..06f306f 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java
@@ -31,7 +31,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTDictionaryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTDictionaryTest.java
index 809e62e..65d0633 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTDictionaryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTDictionaryTest.java
@@ -27,8 +27,8 @@
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 import java.io.IOException;
-import org.opengis.metadata.Identifier;
 import org.opengis.util.FactoryException;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.GeographicCRS;
@@ -374,7 +374,7 @@
          * for checking precedence.
          */
         GeographicCRS crs = factory.createGeographicCRS("2C");
-        Identifier id = TestUtilities.getSingleton(crs.getIdentifiers());
+        ReferenceIdentifier id = TestUtilities.getSingleton(crs.getIdentifiers());
         assertEquals("TEST", id.getCodeSpace());
         assertEquals("21",   id.getCode());
         assertSame(crs, factory.createGeographicCRS("2C"));                         // Test caching.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java
deleted file mode 100644
index cb937db..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java
+++ /dev/null
@@ -1,700 +0,0 @@
-/*
- * 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.io.wkt;
-
-import org.opengis.referencing.cs.CoordinateSystem;
-import org.opengis.referencing.crs.CRSFactory;
-import org.opengis.referencing.crs.VerticalCRS;
-import org.opengis.referencing.datum.VerticalDatumType;
-import org.opengis.util.FactoryException;
-import org.opengis.test.wkt.CRSParserTest;
-import org.apache.sis.internal.metadata.AxisNames;
-import org.apache.sis.test.TestRunner;
-import org.apache.sis.test.DependsOn;
-import org.junit.Test;
-import org.junit.Ignore;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Tests Well-Known Text parser using the tests defined in GeoAPI. Those tests use the
- * {@link org.apache.sis.referencing.factory.GeodeticObjectFactory#createFromWKT(String)} method.
- *
- * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
- * @since   0.6
- * @module
- */
-@RunWith(TestRunner.class)
-@DependsOn(GeodeticObjectParserTest.class)
-public final strictfp class WKTParserTest extends CRSParserTest {
-    /**
-     * Whether the test should replace the curly quotation marks “ and ” by the straight quotation mark ".
-     * The ISO 19162 specification uses only straight quotation marks, but SIS supports both.
-     * Curly quotation marks are convenient for identifying bugs, so we test them first.
-     */
-    private boolean useStraightQuotes;
-
-    /**
-     * Creates a new test case using the default {@code CRSFactory} implementation.
-     */
-    public WKTParserTest() {
-        super(org.apache.sis.internal.system.DefaultFactories.forClass(CRSFactory.class));
-    }
-
-    /**
-     * Pre-process the WKT string before parsing. This method may replace curly quotation marks
-     * ({@code “} and {@code ”}) by straight quotation marks ({@code "}).
-     * The Apache SIS parser should understand both forms transparently.
-     *
-     * @param  wkt  the Well-Known Text to pre-process.
-     * @return the Well-Known Text to parse.
-     */
-    @Override
-    protected String preprocessWKT(String wkt) {
-        if (useStraightQuotes) {
-            wkt = super.preprocessWKT(wkt);
-        }
-        return wkt;
-    }
-
-    /**
-     * Verifies the axis names of a geographic CRS. This method is invoked when the parsed object is
-     * expected to have <cite>"Geodetic latitude"</cite> and <cite>"Geodetic longitude"</cite> names.
-     */
-    @SuppressWarnings("fallthrough")
-    private void verifyEllipsoidalCS() {
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        switch (cs.getDimension()) {
-            default: assertEquals("name", AxisNames.ELLIPSOIDAL_HEIGHT, cs.getAxis(2).getName().getCode());
-            case 2:  assertEquals("name", AxisNames.GEODETIC_LONGITUDE, cs.getAxis(1).getName().getCode());
-            case 1:  assertEquals("name", AxisNames.GEODETIC_LATITUDE,  cs.getAxis(0).getName().getCode());
-            case 0:  break;
-        }
-        switch (cs.getDimension()) {
-            default: assertEquals("abbreviation", "h", cs.getAxis(2).getAbbreviation());
-            case 2:  assertEquals("abbreviation", "λ", cs.getAxis(1).getAbbreviation());
-            case 1:  assertEquals("abbreviation", "φ", cs.getAxis(0).getAbbreviation());
-            case 0:  break;
-        }
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   GEODCRS[“WGS 84”,
-     *    DATUM[“World Geodetic System 1984”,
-     *      ELLIPSOID[“WGS 84”, 6378137, 298.257223563,
-     *        LENGTHUNIT[“metre”,1.0]]],
-     *    CS[ellipsoidal,3],
-     *      AXIS[“(lat)”,north,ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      AXIS[“(lon)”,east,ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      AXIS[“ellipsoidal height (h)”,up,LENGTHUNIT[“metre”,1.0]]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographic3D() throws FactoryException {
-        super.testGeographic3D();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographic3D();                           // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   GEODCRS[“S-95”,
-     *    DATUM[“Pulkovo 1995”,
-     *      ELLIPSOID[“Krassowsky 1940”, 6378245, 298.3,
-     *        LENGTHUNIT[“metre”,1.0]]],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north,ORDER[1]],
-     *      AXIS[“longitude”,east,ORDER[2]],
-     *      ANGLEUNIT[“degree”,0.0174532925199433],
-     *    REMARK[“Система Геодеэических Координвт года 1995(СК-95)”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographicWithUnicode() throws FactoryException {
-        super.testGeographicWithUnicode();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographicWithUnicode();                  // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   GEODCRS[“NAD83”,
-     *    DATUM[“North American Datum 1983”,
-     *      ELLIPSOID[“GRS 1980”, 6378137, 298.257222101, LENGTHUNIT[“metre”,1.0]]],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north],
-     *      AXIS[“longitude”,east],
-     *      ANGLEUNIT[“degree”,0.017453292519943],
-     *    ID[“EPSG”,4269],
-     *    REMARK[“1986 realisation”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographicWithIdentifier() throws FactoryException {
-        super.testGeographicWithIdentifier();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographicWithIdentifier();               // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   GEODCRS[“NTF (Paris)”,
-     *    DATUM[“Nouvelle Triangulation Francaise”,
-     *      ELLIPSOID[“Clarke 1880 (IGN)”, 6378249.2, 293.4660213]],
-     *    PRIMEM[“Paris”,2.5969213],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north,ORDER[1]],
-     *      AXIS[“longitude”,east,ORDER[2]],
-     *      ANGLEUNIT[“grad”,0.015707963267949],
-     *    REMARK[“Nouvelle Triangulation Française”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographicWithGradUnits() throws FactoryException {
-        super.testGeographicWithGradUnits();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographicWithGradUnits();                // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   GEODETICCRS[“JGD2000”,
-     *    DATUM[“Japanese Geodetic Datum 2000”,
-     *      ELLIPSOID[“GRS 1980”, 6378137, 298.257222101]],
-     *    CS[Cartesian,3],
-     *      AXIS[“(X)”,geocentricX],
-     *      AXIS[“(Y)”,geocentricY],
-     *      AXIS[“(Z)”,geocentricZ],
-     *      LENGTHUNIT[“metre”,1.0],
-     *    SCOPE[“Geodesy, topographic mapping and cadastre”],
-     *    AREA[“Japan”],
-     *    BBOX[17.09,122.38,46.05,157.64],
-     *    TIMEEXTENT[2002-04-01,2011-10-21],
-     *    ID[“EPSG”,4946,URI[“urn:ogc:def:crs:EPSG::4946”]],
-     *    REMARK[“注:JGD2000ジオセントリックは現在JGD2011に代わりました。”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeocentric() throws FactoryException {
-        super.testGeocentric();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.GEOCENTRIC_X, cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.GEOCENTRIC_Y, cs.getAxis(1).getName().getCode());
-        assertEquals("name", AxisNames.GEOCENTRIC_Z, cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testGeocentric();                             // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Ignored for now, because the Lambert Azimuthal Equal Area projection method is not yet implemented.
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    @Ignore("Lambert Azimuthal Equal Area projection method not yet implemented.")
-    public void testProjectedYX() throws FactoryException {
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   PROJCRS[“NAD27 / Texas South Central”,
-     *    BASEGEODCRS[“NAD27”,
-     *      DATUM[“North American Datum 1927”,
-     *        ELLIPSOID[“Clarke 1866”, 20925832.164, 294.97869821,
-     *          LENGTHUNIT[“US survey foot”,0.304800609601219]]]],
-     *    CONVERSION[“Texas South Central SPCS27”,
-     *      METHOD[“Lambert Conic Conformal (2SP)”,ID[“EPSG”,9802]],
-     *      PARAMETER[“Latitude of false origin”,27.83333333333333,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8821]],
-     *      PARAMETER[“Longitude of false origin”,-99.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8822]],
-     *      PARAMETER[“Latitude of 1st standard parallel”,28.383333333333,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8823]],
-     *      PARAMETER[“Latitude of 2nd standard parallel”,30.283333333333,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8824]],
-     *      PARAMETER[“Easting at false origin”,2000000.0,
-     *        LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8826]],
-     *      PARAMETER[“Northing at false origin”,0.0,
-     *        LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8827]]],
-     *    CS[Cartesian,2],
-     *      AXIS[“(x)”,east],
-     *      AXIS[“(y)”,north],
-     *      LENGTHUNIT[“US survey foot”,0.304800609601219],
-     *    REMARK[“Fundamental point: Meade’s Ranch KS, latitude 39°13'26.686"N, longitude 98°32'30.506"W.”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testProjectedWithFootUnits() throws FactoryException {
-        super.testProjectedWithFootUnits();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.EASTING,  cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.NORTHING, cs.getAxis(1).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testProjectedWithFootUnits();                  // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters and the line feed in {@code REMARK}):
-     *
-     * <blockquote><pre>PROJCRS[“NAD83 UTM 10”,
-     *  BASEGEODCRS[“NAD83(86)”,
-     *    DATUM[“North American Datum 1983”,
-     *      ELLIPSOID[“GRS 1980”,6378137,298.257222101]],
-     *    ANGLEUNIT[“degree”,0.0174532925199433],
-     *    PRIMEM[“Greenwich”,0]],
-     *  CONVERSION[“UTM zone 10N”,ID[“EPSG”,16010],
-     *    METHOD[“Transverse Mercator”],
-     *    PARAMETER[“Latitude of natural origin”,0.0],
-     *    PARAMETER[“Longitude of natural origin”,-123.0],
-     *    PARAMETER[“Scale factor”,0.9996],
-     *    PARAMETER[“False easting”,500000.0],
-     *    PARAMETER[“False northing”,0.0]],
-     *  CS[Cartesian,2],
-     *    AXIS[“(E)”,east,ORDER[1]],
-     *    AXIS[“(N)”,north,ORDER[2]],
-     *    LENGTHUNIT[“metre”,1.0],
-     *  REMARK[“In this example units are implied. This is allowed for backward compatibility.
-     *          It is recommended that units are explicitly given in the string,
-     *          as in the previous two examples.”]]</pre></blockquote>
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testProjectedWithImplicitParameterUnits() throws FactoryException {
-        super.testProjectedWithImplicitParameterUnits();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.EASTING,  cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.NORTHING, cs.getAxis(1).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testProjectedWithImplicitParameterUnits();    // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis name and vertical datum type.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   VERTCRS[“NAVD88”,
-     *    VDATUM[“North American Vertical Datum 1988”],
-     *    CS[vertical,1],
-     *      AXIS[“gravity-related height (H)”,up],LENGTHUNIT[“metre”,1.0]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testVertical() throws FactoryException {
-        super.testVertical();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.GRAVITY_RELATED_HEIGHT, cs.getAxis(0).getName().getCode());
-        assertEquals("datumType", VerticalDatumType.GEOIDAL, ((VerticalCRS) object).getDatum().getVerticalDatumType());
-
-        useStraightQuotes = true;
-        super.testVertical();                               // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis name.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   TIMECRS[“GPS Time”,
-     *     TDATUM[“Time origin”,TIMEORIGIN[1980-01-01T00:00:00.0Z]],
-     *     CS[temporal,1],AXIS[“time”,future],TIMEUNIT[“day”,86400.0]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testTemporal() throws FactoryException {
-        super.testTemporal();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.TIME, cs.getAxis(0).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testTemporal();                               // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis name.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   PARAMETRICCRS[“WMO standard atmosphere layer 0”,
-     *     PDATUM[“Mean Sea Level”,ANCHOR[“1013.25 hPa at 15°C”]],
-     *     CS[parametric,1],
-     *     AXIS[“pressure (hPa)”,up],
-     *     PARAMETRICUNIT[“hPa”,100.0]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testParametric() throws FactoryException {
-        super.testParametric();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", "pressure", cs.getAxis(0).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testParametric();                             // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   ENGINEERINGCRS[“Astra Minas Grid”,
-     *    ENGINEERINGDATUM[“Astra Minas”],
-     *    CS[Cartesian,2],
-     *      AXIS[“northing (X)”,north,ORDER[1]],
-     *      AXIS[“westing (Y)”,west,ORDER[2]],
-     *      LENGTHUNIT[“metre”,1.0],
-     *    ID[“EPSG”,5800]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testEngineering() throws FactoryException {
-        super.testEngineering();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.NORTHING, cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.WESTING,  cs.getAxis(1).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testEngineering();                            // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   ENGCRS[“A construction site CRS”,
-     *    EDATUM[“P1”,ANCHOR[“Peg in south corner”]],
-     *    CS[Cartesian,2],
-     *      AXIS[“site east”,southWest,ORDER[1]],
-     *      AXIS[“site north”,southEast,ORDER[2]],
-     *      LENGTHUNIT[“metre”,1.0],
-     *    TIMEEXTENT[“date/time t1”,“date/time t2”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testEngineeringRotated() throws FactoryException {
-        super.testEngineeringRotated();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", "site east",  cs.getAxis(0).getName().getCode());
-        assertEquals("name", "site north", cs.getAxis(1).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testEngineeringRotated();                     // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   ENGCRS[“A ship-centred CRS”,
-     *    EDATUM[“Ship reference point”,ANCHOR[“Centre of buoyancy”]],
-     *    CS[Cartesian,3],
-     *      AXIS[“(x)”,forward],
-     *      AXIS[“(y)”,starboard],
-     *      AXIS[“(z)”,down],
-     *      LENGTHUNIT[“metre”,1.0]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testEngineeringForShip() throws FactoryException {
-        super.testEngineeringForShip();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        /*
-         * In this case we had no axis names, so Apache SIS reused the abbreviations.
-         * This could change in any future SIS version if we update Transliterator.
-         */
-        assertEquals("name", "x", cs.getAxis(0).getName().getCode());
-        assertEquals("name", "y", cs.getAxis(1).getName().getCode());
-        assertEquals("name", "z", cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testEngineeringForShip();                     // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *  GEODCRS[“ETRS89 Lambert Azimuthal Equal Area CRS”,
-     *    BASEGEODCRS[“WGS 84”,
-     *      DATUM[“WGS 84”,
-     *        ELLIPSOID[“WGS 84”,6378137,298.2572236,LENGTHUNIT[“metre”,1.0]]]],
-     *    DERIVINGCONVERSION[“Atlantic pole”,
-     *      METHOD[“Pole rotation”,ID[“Authority”,1234]],
-     *      PARAMETER[“Latitude of rotated pole”,52.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Longitude of rotated pole”,-30.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Axis rotation”,-25.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]]],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north,ORDER[1]],
-     *      AXIS[“longitude”,east,ORDER[2]],
-     *      ANGLEUNIT[“degree”,0.0174532925199433]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testDerivedGeodetic() throws FactoryException {
-        super.testDerivedGeodetic();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testDerivedGeodetic();                        // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *  ENGCRS[“Topocentric example A”,
-     *    BASEGEODCRS[“WGS 84”,
-     *      DATUM[“WGS 84”,
-     *        ELLIPSOID[“WGS 84”, 6378137, 298.2572236, LENGTHUNIT[“metre”,1.0]]]],
-     *    DERIVINGCONVERSION[“Topocentric example A”,
-     *      METHOD[“Geographic/topocentric conversions”,ID[“EPSG”,9837]],
-     *      PARAMETER[“Latitude of topocentric origin”,55.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Longitude of topocentric origin”,5.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Ellipsoidal height of topocentric origin”,0.0,
-     *        LENGTHUNIT[“metre”,1.0]]],
-     *    CS[Cartesian,3],
-     *      AXIS[“Topocentric East (U)”,east,ORDER[1]],
-     *      AXIS[“Topocentric North (V)”,north,ORDER[2]],
-     *      AXIS[“Topocentric height (W)”,up,ORDER[3]],
-     *      LENGTHUNIT[“metre”,1.0]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testDerivedEngineeringFromGeodetic() throws FactoryException {
-        super.testDerivedEngineeringFromGeodetic();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", "Topocentric East",   cs.getAxis(0).getName().getCode());
-        assertEquals("name", "Topocentric North",  cs.getAxis(1).getName().getCode());
-        assertEquals("name", "Topocentric height", cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testDerivedEngineeringFromGeodetic();         // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testDerivedEngineeringFromProjected() throws FactoryException {
-        super.testDerivedEngineeringFromProjected();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        /*
-         * In this case we had no axis names, so Apache SIS reused the abbreviations.
-         * This could change in any future SIS version if we update Transliterator.
-         */
-        assertEquals("name", "I", cs.getAxis(0).getName().getCode());
-        assertEquals("name", "J", cs.getAxis(1).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testDerivedEngineeringFromProjected();        // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *  COMPOUNDCRS[“NAD83 + NAVD88”,
-     *    GEODCRS[“NAD83”,
-     *      DATUM[“North American Datum 1983”,
-     *        ELLIPSOID[“GRS 1980”,6378137,298.257222101,
-     *          LENGTHUNIT[“metre”,1.0]]],
-     *        PRIMEMERIDIAN[“Greenwich”,0],
-     *      CS[ellipsoidal,2],
-     *        AXIS[“latitude”,north,ORDER[1]],
-     *        AXIS[“longitude”,east,ORDER[2]],
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      VERTCRS[“NAVD88”,
-     *        VDATUM[“North American Vertical Datum 1988”],
-     *        CS[vertical,1],
-     *          AXIS[“gravity-related height (H)”,up],
-     *          LENGTHUNIT[“metre”,1]]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testCompoundWithVertical() throws FactoryException {
-        super.testCompoundWithVertical();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.GEODETIC_LATITUDE,      cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.GEODETIC_LONGITUDE,     cs.getAxis(1).getName().getCode());
-        assertEquals("name", AxisNames.GRAVITY_RELATED_HEIGHT, cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testCompoundWithVertical();                   // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *  COMPOUNDCRS[“GPS position and time”,
-     *    GEODCRS[“WGS 84”,
-     *      DATUM[“World Geodetic System 1984”,
-     *        ELLIPSOID[“WGS 84”,6378137,298.257223563]],
-     *      CS[ellipsoidal,2],
-     *        AXIS[“(lat)”,north,ORDER[1]],
-     *        AXIS[“(lon)”,east,ORDER[2]],
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *    TIMECRS[“GPS Time”,
-     *      TIMEDATUM[“Time origin”,TIMEORIGIN[1980-01-01]],
-     *      CS[temporal,1],
-     *        AXIS[“time (T)”,future],
-     *        TIMEUNIT[“day”,86400]]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testCompoundWithTime() throws FactoryException {
-        super.testCompoundWithTime();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.GEODETIC_LATITUDE,  cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.GEODETIC_LONGITUDE, cs.getAxis(1).getName().getCode());
-        assertEquals("name", AxisNames.TIME,               cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testCompoundWithTime();                       // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *  COMPOUNDCRS[“ICAO layer 0”,
-     *    GEODETICCRS[“WGS 84”,
-     *      DATUM[“World Geodetic System 1984”,
-     *        ELLIPSOID[“WGS 84”,6378137,298.257223563,
-     *          LENGTHUNIT[“metre”,1.0]]],
-     *      CS[ellipsoidal,2],
-     *        AXIS[“latitude”,north,ORDER[1]],
-     *        AXIS[“longitude”,east,ORDER[2]],
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *    PARAMETRICCRS[“WMO standard atmosphere”,
-     *      PARAMETRICDATUM[“Mean Sea Level”,
-     *        ANCHOR[“Mean Sea Level = 1013.25 hPa”]],
-     *          CS[parametric,1],
-     *            AXIS[“pressure (P)”,unspecified],
-     *            PARAMETRICUNIT[“hPa”,100]]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testCompoundWithParametric() throws FactoryException {
-        super.testCompoundWithParametric();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.GEODETIC_LATITUDE,  cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.GEODETIC_LONGITUDE, cs.getAxis(1).getName().getCode());
-        assertEquals("name", "pressure",                   cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testCompoundWithParametric();                 // Test again with “ and ” replaced by ".
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java b/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
index 629f3d3..d9c709e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
@@ -20,7 +20,6 @@
 import java.util.List;
 import java.util.HashMap;
 import java.util.Collections;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.parameter.ParameterNotFoundException;
 import org.apache.sis.internal.util.Constants;
@@ -105,7 +104,6 @@
      */
     @Test
     public void validateTestObjects() {
-        assertEquals(ParameterDirection.IN, M1_M1_O1_O2.getDirection());
         for (final GeneralParameterDescriptor descriptor : M1_M1_O1_O2.descriptors()) {
             AssertionError error = null;
             try {
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/parameter/ParametersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/parameter/ParametersTest.java
index 31aacc3..e39d6a4 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/parameter/ParametersTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/parameter/ParametersTest.java
@@ -21,7 +21,6 @@
 import java.util.Collections;
 import javax.measure.Unit;
 import org.opengis.parameter.ParameterDescriptor;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.parameter.ParameterValueGroup;
@@ -119,8 +118,6 @@
             @Override public Collection<GenericName>  getAlias()         {return descriptor.getAlias();}
             @Override public Set<ReferenceIdentifier> getIdentifiers()   {return descriptor.getIdentifiers();}
             @Override public InternationalString      getRemarks()       {return descriptor.getRemarks();}
-            @Override public InternationalString      getDescription()   {return descriptor.getDescription();}
-            @Override public ParameterDirection       getDirection()     {return descriptor.getDirection();}
             @Override public int                      getMinimumOccurs() {return descriptor.getMinimumOccurs();}
             @Override public int                      getMaximumOccurs() {return descriptor.getMaximumOccurs();}
             @Override public Class<T>                 getValueClass()    {return descriptor.getValueClass();}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/parameter/TensorParametersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/parameter/TensorParametersTest.java
index dd4f198..a89901e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/parameter/TensorParametersTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/parameter/TensorParametersTest.java
@@ -30,7 +30,6 @@
 import org.junit.Test;
 
 import static java.util.Collections.singletonMap;
-import static org.opengis.test.Validators.validate;
 import static org.apache.sis.test.ReferencingAssert.*;
 import static org.apache.sis.internal.util.Constants.NUM_ROW;
 import static org.apache.sis.internal.util.Constants.NUM_COL;
@@ -321,7 +320,6 @@
                 }
                 final ParameterValueGroup group = param.createValueGroup(
                         singletonMap(ParameterDescriptor.NAME_KEY, "Test"), matrix);
-                validate(group);
                 assertEquals(NUM_ROW,    numRow, group.parameter(NUM_ROW).intValue());
                 assertEquals(NUM_COL,    numCol, group.parameter(NUM_COL).intValue());
                 assertEquals("elements", matrix, param.toMatrix(group));
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java
index f00a858..a88283e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java
@@ -24,6 +24,7 @@
 import org.opengis.util.NameFactory;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.internal.simple.SimpleCitation;
 import org.apache.sis.internal.simple.SimpleIdentifier;
 import org.apache.sis.internal.system.DefaultFactories;
@@ -87,17 +88,17 @@
         /*
          * The failed attempt to set a new codespace shall not have modified builder state.
          */
-        assertEquals("EPSG",         builder.properties.get(Identifier.CODESPACE_KEY));
+        assertEquals("EPSG",         builder.properties.get(ReferenceIdentifier.CODESPACE_KEY));
         assertSame  (Citations.EPSG, builder.properties.get(Identifier.AUTHORITY_KEY));
         /*
          * After a cleanup (normally after a createXXX(…) method call), user shall be allowed to
          * set a new codespace again. Note that the cleanup operation shall not clear the codespace.
          */
         builder.onCreate(true);
-        assertEquals("EPSG",         builder.properties.get(Identifier.CODESPACE_KEY));
+        assertEquals("EPSG",         builder.properties.get(ReferenceIdentifier.CODESPACE_KEY));
         assertSame  (Citations.EPSG, builder.properties.get(Identifier.AUTHORITY_KEY));
         builder.setCodeSpace(IOGP, "EPSG");
-        assertEquals("EPSG", builder.properties.get(Identifier.CODESPACE_KEY));
+        assertEquals("EPSG", builder.properties.get(ReferenceIdentifier.CODESPACE_KEY));
         assertSame  ( IOGP,  builder.properties.get(Identifier.AUTHORITY_KEY));
     }
 
@@ -224,7 +225,7 @@
                     assertSame("Authority and codespace shall be unchanged.", Citations.EPSG, value);
                     break;
                 }
-                case Identifier.CODESPACE_KEY: {
+                case ReferenceIdentifier.CODESPACE_KEY: {
                     assertEquals("Authority and codespace shall be unchanged.", "EPSG", value);
                     break;
                 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
index 547d069..4683a86 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
@@ -233,7 +233,7 @@
             final String        name  = e.name();
             final VerticalDatum datum = e.datum();
             final VerticalCRS   crs   = e.crs();
-            if (e.isEPSG) {
+            if (e.isEPSG && !name.startsWith("NAV")) {
                 /*
                  * BAROMETRIC, ELLIPSOIDAL and OTHER_SURFACE uses an axis named "Height", which is not
                  * a valid axis name according ISO 19111. We skip the validation test for those enums.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java
index 4f9df67..80fc5ba 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodesicsOnEllipsoidTest.java
@@ -49,6 +49,11 @@
 @DependsOn(GeodeticCalculatorTest.class)
 public final strictfp class GeodesicsOnEllipsoidTest extends GeodeticCalculatorTest {
     /**
+     * Tolerance threshold for comparison of floating point numbers.
+     */
+    private static final double STRICT = 0;
+
+    /**
      * The {@link GeodesicsOnEllipsoid} instance to be tested.
      * A specialized type is used for tracking locale variables.
      */
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodeticCalculatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodeticCalculatorTest.java
index 57e6980..2e7e9ec 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodeticCalculatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodeticCalculatorTest.java
@@ -26,7 +26,6 @@
 import java.io.IOException;
 import java.io.LineNumberReader;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.referencing.j2d.ShapeUtilitiesExt;
@@ -234,8 +233,6 @@
     @DependsOnMethod("testGeodesicDistanceAndAzimuths")
     public void testUsingTransform() {
         final GeodeticCalculator c = create(true);
-        assertAxisDirectionsEqual("GeographicCRS", c.getGeographicCRS().getCoordinateSystem(), AxisDirection.NORTH, AxisDirection.EAST);
-        assertAxisDirectionsEqual("PositionCRS",     c.getPositionCRS().getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH);
         final double φ = -33.0;
         final double λ = -71.6;
         c.setStartPoint(new DirectPosition2D(λ, φ));
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/ImmutableIdentifierTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/ImmutableIdentifierTest.java
index 7943992..8555c79 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/ImmutableIdentifierTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/ImmutableIdentifierTest.java
@@ -33,7 +33,7 @@
 import org.junit.Test;
 
 import static org.apache.sis.test.ReferencingAssert.*;
-import static org.opengis.metadata.Identifier.*;
+import static org.opengis.referencing.ReferenceIdentifier.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java
index f77b2bb..cef7404 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java
@@ -17,11 +17,11 @@
 package org.apache.sis.referencing;
 
 import java.util.Locale;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.util.InternationalString;
 import org.opengis.util.NameSpace;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
-import org.opengis.metadata.Identifier;
 import org.opengis.test.Validators;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.internal.system.DefaultFactories;
@@ -49,7 +49,7 @@
     @Test
     public void testCreateFromCode() {
         final NamedIdentifier identifier = new NamedIdentifier(Citations.EPSG, "EPSG", "4326", "8.3", null);
-        Validators.validate((Identifier)  identifier);
+        Validators.validate((ReferenceIdentifier) identifier);
         Validators.validate((GenericName) identifier);
 
         // ImmutableIdentifier properties
@@ -75,7 +75,7 @@
         final NameFactory factory = DefaultFactories.forBuildin(NameFactory.class);
         final NameSpace scope = factory.createNameSpace(factory.createLocalName(null, "IOGP"), null);
         final NamedIdentifier identifier = new NamedIdentifier(factory.createGenericName(scope, "EPSG", "4326"));
-        Validators.validate((Identifier)  identifier);
+        Validators.validate((ReferenceIdentifier) identifier);
         Validators.validate((GenericName) identifier);
 
         // ImmutableIdentifier properties
@@ -113,7 +113,7 @@
     @DependsOnMethod("testCreateFromCode")
     public void testCreateFromInternationalString() {
         final NamedIdentifier identifier = createI18N();
-        Validators.validate((Identifier)  identifier);
+        Validators.validate((ReferenceIdentifier) identifier);
         Validators.validate((GenericName) identifier);
 
         // ImmutableIdentifier properties
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java
index a421004..003ed81 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java
@@ -115,7 +115,7 @@
     @Test
     public void testConstruction() {
         final DefaultDerivedCRS crs = createLongitudeRotation();
-        Validators.validate(crs);
+//      Validators.validate(crs);
 
         assertEquals("name",    "Back to Greenwich",                crs.getName().getCode());
         assertEquals("baseCRS", "NTF (Paris)",                      crs.getBaseCRS().getName().getCode());
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
index 36dec06..d0bbfee 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
@@ -17,10 +17,10 @@
 package org.apache.sis.referencing.crs;
 
 import org.opengis.test.Validators;
-import org.opengis.metadata.Identifier;
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.io.wkt.Convention;
@@ -92,7 +92,7 @@
     @Test
     public void testIdentifiers() {
         GeographicCRS crs = CommonCRS.WGS72.geographic();
-        Identifier identifier = getSingleton(crs.getIdentifiers());
+        ReferenceIdentifier identifier = getSingleton(crs.getIdentifiers());
         assertEquals("codespace", "EPSG", identifier.getCodeSpace());
         assertEquals("code",      "4322", identifier.getCode());
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java
index b23b658..84edb50 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java
@@ -48,8 +48,8 @@
     public void validate() {
         final ValidatorContainer validators = new ValidatorContainer();
         validators.validate(WGS84);
-        validators.validate(WGS84_3D);           validators.crs.enforceStandardNames = false;
-        validators.validate(ELLIPSOIDAL_HEIGHT); validators.crs.enforceStandardNames = true;
+        validators.validate(WGS84_3D);
+        validators.validate(ELLIPSOIDAL_HEIGHT);
         validators.validate(GRAVITY_RELATED_HEIGHT);
         validators.validate(TIME);
         validators.validate(SPHERICAL);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java
index 9ddabab..a85b055 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java
@@ -25,7 +25,7 @@
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultPolarCSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
index 4fe23f6..5eca8ce 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
@@ -25,7 +25,7 @@
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java
index 1b994f6..7a8ed21 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java
@@ -23,7 +23,7 @@
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/datum/GeodeticDatumMock.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/datum/GeodeticDatumMock.java
index 43b9c17..86f481f 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/datum/GeodeticDatumMock.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/datum/GeodeticDatumMock.java
@@ -16,8 +16,11 @@
  */
 package org.apache.sis.referencing.datum;
 
+import java.util.Date;
 import javax.measure.Unit;
 import javax.measure.quantity.Length;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.extent.Extent;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.datum.PrimeMeridian;
@@ -123,12 +126,16 @@
         return new Object[] {getCode(), alias, semiMajorAxis, semiMinorAxis, inverseFlattening, isIvfDefinitive};
     }
 
-    @Override public PrimeMeridian getPrimeMeridian()     {return PrimeMeridianMock.GREENWICH;}
-    @Override public Ellipsoid     getEllipsoid()         {return this;}
-    @Override public Unit<Length>  getAxisUnit()          {return Units.METRE;}
-    @Override public double        getSemiMajorAxis()     {return semiMajorAxis;}
-    @Override public double        getSemiMinorAxis()     {return semiMinorAxis;}
-    @Override public double        getInverseFlattening() {return inverseFlattening;}
-    @Override public boolean       isSphere()             {return semiMajorAxis == semiMinorAxis;}
-    @Override public boolean       isIvfDefinitive()      {return isIvfDefinitive;}
+    @Override public PrimeMeridian        getPrimeMeridian()      {return PrimeMeridianMock.GREENWICH;}
+    @Override public Ellipsoid            getEllipsoid()          {return this;}
+    @Override public Unit<Length>         getAxisUnit()           {return Units.METRE;}
+    @Override public double               getSemiMajorAxis()      {return semiMajorAxis;}
+    @Override public double               getSemiMinorAxis()      {return semiMinorAxis;}
+    @Override public double               getInverseFlattening()  {return inverseFlattening;}
+    @Override public boolean              isSphere()              {return semiMajorAxis == semiMinorAxis;}
+    @Override public boolean              isIvfDefinitive()       {return isIvfDefinitive;}
+    @Override public InternationalString  getAnchorPoint()        {return null;}
+    @Override public Date                 getRealizationEpoch()   {return null;}
+    @Override public Extent               getDomainOfValidity()   {return null;}
+    @Override public InternationalString  getScope()              {return null;}
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
index 719fa84..3363e59 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
@@ -363,7 +363,7 @@
                 "  SCOPE[“Horizontal component of 3D system.\\E.*\\Q”],\n" +
                 "  AREA[“World\\E.*\\Q”],\n" +
                 "  BBOX[-90.00, -180.00, 90.00, 180.00],\n" +
-                "  ID[“CRS”, 84, CITATION[“OGC:WMS”], URI[“urn:ogc:def:crs:OGC:1.3:CRS84”]]]\\E", crs);
+                "  ID[“CRS”, 84, CITATION[“WMS”], URI[“urn:ogc:def:crs:OGC:1.3:CRS84”]]]\\E", crs);
         /*
          * Note: the WKT specification defines the ID element as:
          *
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2001.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2001.java
deleted file mode 100644
index 5013bcf..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2001.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-
-// Test imports
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createUnit(String)}.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead of in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
- * @since   0.7
- * @module
- */
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2001 extends org.opengis.test.referencing.gigs.GIGS2001 {
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2001() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2002.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2002.java
deleted file mode 100644
index e441e57..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2002.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-import org.apache.sis.internal.system.Loggers;
-
-// Test imports
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-import org.apache.sis.test.LoggingWatcher;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createEllipsoid(String)}.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead of in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2001.class,     // Units created from EPSG codes
-    GIGS3002.class      // Ellipsoids created from properties
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2002 extends org.opengis.test.referencing.gigs.GIGS2002 {
-    /**
-     * A JUnit {@link Rule} for listening to log events. This field is public because JUnit requires us to
-     * do so, but should be considered as an implementation details (it should have been a private field).
-     */
-    @Rule
-    public final LoggingWatcher loggings = new LoggingWatcher(Loggers.CRS_FACTORY);
-
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2002() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testClarkeMichigan() throws FactoryException {
-        super.testClarkeMichigan();
-        loggings.assertNextLogContains("EPSG:7009");
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testPopularVisualisationSphere() throws FactoryException {
-        super.testPopularVisualisationSphere();
-        loggings.assertNextLogContains("EPSG:7059");
-    }
-
-    /**
-     * Verifies that no unexpected warning has been emitted in this test.
-     */
-    @After
-    public void assertNoUnexpectedLog() {
-        loggings.assertNoUnexpectedLog();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2003.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2003.java
deleted file mode 100644
index 8720875..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2003.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-
-// Test imports
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createPrimeMeridian(String)}.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead of in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2001.class,     // Units created from EPSG codes
-    GIGS3003.class      // Prime meridians created from properties
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2003 extends org.opengis.test.referencing.gigs.GIGS2003 {
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2003() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2004.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2004.java
deleted file mode 100644
index ff26494..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2004.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.IdentifiedObject;
-import org.apache.sis.internal.system.Loggers;
-import org.apache.sis.util.CharSequences;
-
-// Test imports
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-import org.apache.sis.test.LoggingWatcher;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateReferenceSystem(String)}
- * for geodetic (geographic or geocentric) CRS.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead of in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2002.class,     // Ellipsoids created from EPSG codes
-    GIGS2003.class,     // Prime meridians created from EPSG codes
-    GIGS3004.class      // Geodetic datums created from properties
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2004 extends org.opengis.test.referencing.gigs.GIGS2004 {
-    /**
-     * A JUnit {@link Rule} for listening to log events. This field is public because JUnit requires us to
-     * do so, but should be considered as an implementation details (it should have been a private field).
-     */
-    @Rule
-    public final LoggingWatcher loggings = new LoggingWatcher(Loggers.CRS_FACTORY);
-
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2004() {
-        super(TestFactorySource.factory, TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-
-    /**
-     * Removes the accented characters from the object name, so it can be compared against the expected name.
-     *
-     * @param  object  the object from which to get a name that can be verified against the expected name.
-     * @return the name of the given object, eventually modified in order to match the expected name.
-     */
-    @Override
-    protected String getVerifiableName(final IdentifiedObject object) {
-        return CharSequences.toASCII(super.getVerifiableName(object)).toString();
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testNAD27_Michigan() throws FactoryException {
-        super.testNAD27_Michigan();
-        loggings.assertNextLogContains("EPSG:6268");    // Datum replaced by 6267
-        loggings.skipNextLogIfContains("EPSG:7009");    // Ellipsoid replaced by 7008 (may have been created by GIGS2002)
-        loggings.assertNextLogContains("EPSG:4268");    // CRS replaced by 4267
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testPopularVisualisation() throws FactoryException {
-        super.testPopularVisualisation();
-        loggings.assertNextLogContains("EPSG:6055");
-        loggings.skipNextLogIfContains("EPSG:7059");    // Ellipsoid may have been created by GIGS2002.
-        loggings.assertNextLogContains("EPSG:4055");
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testPadang() throws FactoryException {
-        super.testPadang();
-        loggings.assertNextLogContains("EPSG:6280");        // Datum
-        loggings.assertNextLogContains("EPSG:4280");        // CRS
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testPadang_Jakarta() throws FactoryException {
-        super.testPadang_Jakarta();
-        loggings.assertNextLogContains("EPSG:6808");        // Datum
-        loggings.assertNextLogContains("EPSG:4808");        // CRS
-    }
-
-    /**
-     * Verifies that no unexpected warning has been emitted in this test.
-     */
-    @After
-    public void assertNoUnexpectedLog() {
-        loggings.assertNoUnexpectedLog();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2005.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2005.java
deleted file mode 100644
index 2b9dd45..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2005.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-import org.apache.sis.internal.system.Loggers;
-
-// Test imports
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-import org.apache.sis.test.LoggingWatcher;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateOperation(String)}
- * for map projections.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead of in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2001.class,     // Units created from EPSG codes
-    GIGS3005.class      // Conversions created from properties
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2005 extends org.opengis.test.referencing.gigs.GIGS2005 {
-    /**
-     * A JUnit {@link Rule} for listening to log events. This field is public because JUnit requires us to
-     * do so, but should be considered as an implementation details (it should have been a private field).
-     */
-    @Rule
-    public final LoggingWatcher loggings = new LoggingWatcher(Loggers.CRS_FACTORY);
-
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2005() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testAustralianMapGridZones() throws FactoryException {
-        super.testAustralianMapGridZones();
-        loggings.assertNextLogContains("EPSG:17448");    // Falls outside EEZ area.
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testUSStatePlaneZones_LCC() throws FactoryException {
-        super.testUSStatePlaneZones_LCC();
-        loggings.assertNextLogContains("EPSG:12112");    // Method changed to accord with NGS practice.
-        loggings.assertNextLogContains("EPSG:12113");
-    }
-
-    /**
-     * Verifies that no unexpected warning has been emitted in this test.
-     */
-    @After
-    public void assertNoUnexpectedLog() {
-        loggings.assertNoUnexpectedLog();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2006.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2006.java
deleted file mode 100644
index 1e804e1..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2006.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-import org.apache.sis.internal.system.Loggers;
-
-// Test imports
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-import org.apache.sis.test.LoggingWatcher;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateReferenceSystem(String)}
- * for projected CRS.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead of in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2004.class,     // Geodetic CRSs created from EPSG codes
-    GIGS2005.class      // Conversions created from EPSG codes
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2006 extends org.opengis.test.referencing.gigs.GIGS2006 {
-    /**
-     * A JUnit {@link Rule} for listening to log events. This field is public because JUnit requires us to
-     * do so, but should be considered as an implementation details (it should have been a private field).
-     */
-    @Rule
-    public final LoggingWatcher loggings = new LoggingWatcher(Loggers.CRS_FACTORY);
-
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2006() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testNAD27_Michigan() throws FactoryException {
-        super.testNAD27_Michigan();
-        loggings.skipNextLogIfContains("EPSG:6268");     // Datum replaced by 6267
-        loggings.skipNextLogIfContains("EPSG:7009");     // Ellipsoid replaced by 7008
-        loggings.skipNextLogIfContains("EPSG:4268");     // CRS replaced by 4267
-        loggings.assertNextLogContains("EPSG:12111");
-        loggings.assertNextLogContains("EPSG:26811");    // ProjectedCRS (no replacement).
-        loggings.skipNextLogIfContains("EPSG:12112");    // Conversion replaced by 6198
-        loggings.assertNextLogContains("EPSG:26812");    // ProjectedCRS replaced by 6201
-        loggings.skipNextLogIfContains("EPSG:12113");    // Conversion replaced by 6199
-        loggings.assertNextLogContains("EPSG:26813");    // ProjectedCRS replaced by 6202
-    }
-
-    /**
-     * Verifies that no unexpected warning has been emitted in this test.
-     */
-    @After
-    public void assertNoUnexpectedLog() {
-        loggings.assertNoUnexpectedLog();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2007.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2007.java
deleted file mode 100644
index 04d074d..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2007.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-
-// Test imports
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateOperation(String)}
- * for horizontal coordinate transformations.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead of in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2006.class      // Projected CRSs created from EPSG codes
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2007 extends org.opengis.test.referencing.gigs.GIGS2007 {
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2007() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2008.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2008.java
deleted file mode 100644
index b7054c9..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2008.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-
-// Test imports
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateReferenceSystem(String)}
- * for vertical CRS.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead of in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2001.class      // Units created from EPSG codes
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2008 extends org.opengis.test.referencing.gigs.GIGS2008 {
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2008() {
-        super(TestFactorySource.factory, TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2009.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2009.java
deleted file mode 100644
index 90314bb..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2009.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-
-// Test imports
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateOperation(String)}
- * for vertical coordinate transformations.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead of in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2008.class      // Vertical CRSs created from EPSG codes
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2009 extends org.opengis.test.referencing.gigs.GIGS2009 {
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2009() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3002.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3002.java
deleted file mode 100644
index f5dcb9f..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3002.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.referencing.datum.DatumFactory;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.test.DependsOn;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-
-
-/**
- * Runs the <cite>Geospatial Integrity of Geoscience Software</cite> tests on
- * {@link org.apache.sis.referencing.datum.DefaultEllipsoid} objects creation.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
- * @since   0.6
- * @module
- */
-@DependsOn({
-    org.apache.sis.referencing.datum.DefaultEllipsoidTest.class
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS3002 extends org.opengis.test.referencing.gigs.GIGS3002 {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GIGS3002() {
-        super(DefaultFactories.forBuildin(DatumFactory.class));
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3003.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3003.java
deleted file mode 100644
index 7e84715..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3003.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.referencing.datum.DatumFactory;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.test.DependsOn;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-
-
-/**
- * Runs the <cite>Geospatial Integrity of Geoscience Software</cite> tests on
- * {@link org.apache.sis.referencing.datum.DefaultPrimeMeridian} objects creation.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
- * @since   0.6
- * @module
- */
-@DependsOn({
-    org.apache.sis.referencing.datum.DefaultPrimeMeridianTest.class
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS3003 extends org.opengis.test.referencing.gigs.GIGS3003 {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GIGS3003() {
-        super(DefaultFactories.forBuildin(DatumFactory.class));
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3004.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3004.java
deleted file mode 100644
index 4e80d01..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3004.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.referencing.cs.CSFactory;
-import org.opengis.referencing.crs.CRSFactory;
-import org.opengis.referencing.datum.DatumFactory;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.test.DependsOn;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-
-
-/**
- * Runs the <cite>Geospatial Integrity of Geoscience Software</cite> tests on
- * {@link org.apache.sis.referencing.datum.DefaultGeodeticDatum} objects creation.
- * {@code GIGS3004} tests also geographic and geocentric CRS creations with the tested geodetic datum.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
- * @since   0.6
- * @module
- */
-@DependsOn({
-    GIGS3002.class,     // Ellipsoids created from properties
-    GIGS3003.class,     // Prime meridians created from properties
-    org.apache.sis.referencing.datum.DefaultGeodeticDatumTest.class
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS3004 extends org.opengis.test.referencing.gigs.GIGS3004 {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GIGS3004() {
-        super(DefaultFactories.forBuildin(DatumFactory.class),
-              DefaultFactories.forBuildin(CSFactory.class),
-              DefaultFactories.forBuildin(CRSFactory.class));
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3005.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3005.java
deleted file mode 100644
index 9cb3f93..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3005.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import org.opengis.referencing.operation.CoordinateOperationFactory;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.test.DependsOn;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-
-
-/**
- * Runs the <cite>Geospatial Integrity of Geoscience Software</cite> tests on
- * {@link org.apache.sis.referencing.operation.DefaultConversion} objects creation.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
- * @since   0.6
- * @module
- */
-@DependsOn({
-    org.apache.sis.referencing.operation.DefaultConversionTest.class
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS3005 extends org.opengis.test.referencing.gigs.GIGS3005 {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GIGS3005() {
-        super(DefaultFactories.forBuildin(CoordinateOperationFactory.class));
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java
deleted file mode 100644
index ce22109..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * 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.referencing.factory;
-
-import java.util.Map;
-import java.util.Collections;
-import javax.measure.Unit;
-import javax.measure.quantity.Angle;
-import javax.measure.quantity.Length;
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.cs.CSFactory;
-import org.opengis.referencing.cs.CartesianCS;
-import org.opengis.referencing.cs.EllipsoidalCS;
-import org.opengis.referencing.cs.AxisDirection;
-import org.opengis.referencing.cs.CoordinateSystemAxis;
-import org.opengis.referencing.crs.CRSFactory;
-import org.opengis.referencing.crs.GeodeticCRS;
-import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.referencing.crs.ProjectedCRS;
-import org.opengis.referencing.datum.DatumFactory;
-import org.opengis.referencing.datum.Ellipsoid;
-import org.opengis.referencing.datum.PrimeMeridian;
-import org.opengis.referencing.datum.GeodeticDatum;
-import org.opengis.referencing.operation.CoordinateOperationFactory;
-import org.opengis.referencing.operation.OperationMethod;
-import org.opengis.referencing.operation.Conversion;
-import org.opengis.parameter.ParameterValueGroup;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.referencing.operation.DefaultConversion;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.io.wkt.Convention;
-import org.apache.sis.measure.Units;
-
-// Test dependencies
-import org.opengis.test.referencing.ObjectFactoryTest;
-import org.apache.sis.test.TestRunner;
-import org.apache.sis.test.DependsOn;
-import org.junit.runner.RunWith;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.apache.sis.test.ReferencingAssert.*;
-
-
-/**
- * Tests {@link GeodeticObjectFactory} using the suite of tests provided in the GeoAPI project.
- * Note that this does not include authority factories tests or GIGS tests.
- *
- * @author  Cédric Briançon (Geomatys)
- * @version 0.7
- * @since   0.6
- * @module
- */
-@RunWith(TestRunner.class)
-@DependsOn({
-    org.apache.sis.referencing.crs.DefaultGeocentricCRSTest.class,
-    org.apache.sis.referencing.crs.DefaultGeographicCRSTest.class,
-    org.apache.sis.referencing.crs.DefaultProjectedCRSTest.class
-})
-public final strictfp class GeodeticObjectFactoryTest extends ObjectFactoryTest {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GeodeticObjectFactoryTest() {
-        super(DefaultFactories.forBuildin(DatumFactory.class),
-              DefaultFactories.forBuildin(CSFactory   .class),
-              DefaultFactories.forBuildin(CRSFactory  .class),
-              DefaultFactories.forBuildin(CoordinateOperationFactory.class));
-    }
-
-    @Override
-    @Deprecated
-    @Ignore("Replaced by testProjectedWithGeoidalHeight()")
-    public void testProjected3D() throws FactoryException {
-    }
-
-    /**
-     * Tests {@link GeodeticObjectFactory#createFromWKT(String)}. We test only a very small WKT here because
-     * it is not the purpose of this class to test the parser. The main purpose of this test is to verify
-     * that {@link GeodeticObjectFactory} has been able to instantiate the parser.
-     *
-     * @throws FactoryException if the parsing failed.
-     */
-    @Test
-    public void testCreateFromWKT() throws FactoryException {
-        final GeodeticCRS crs = (GeodeticCRS) crsFactory.createFromWKT(
-                "GEOGCS[“WGS 84”,\n" +
-                "  DATUM[“World Geodetic System 1984”,\n" +
-                "    SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
-                "  PRIMEM[“Greenwich”, 0.0],\n" +
-                "  UNIT[“degree”, 0.017453292519943295]]");
-
-        assertEquals("name",  "WGS 84", crs.getName().getCode());
-        assertEquals("datum", "World Geodetic System 1984", crs.getDatum().getName().getCode());
-    }
-
-    /**
-     * Tests {@link GeodeticObjectFactory#createFromWKT(String)} with an erroneous projection parameter name.
-     * The intent is to verify that the expected exception is thrown.
-     *
-     * @throws FactoryException if the parsing failed for another reason than the expected one.
-     */
-    @Test
-    public void testInvalidParameterInWKT() throws FactoryException {
-        try {
-            crsFactory.createFromWKT(
-                "PROJCRS[“Custom”,\n" +
-                "  BASEGEODCRS[“North American 1983”,\n" +
-                "    DATUM[“North American 1983”,\n" +
-                "      ELLIPSOID[“GRS 1980”, 6378137, 298.257222101]]],\n" +
-                "  CONVERSION[“Custom”,\n" +
-                "    METHOD[“Lambert Conformal Conic”],\n" +
-                "    PARAMETER[“Standard parallel 1”, 43.0],\n" +
-                "    PARAMETER[“Standard parallel 2”, 45.5],\n" +
-                "    PARAMETER[“Central parallel”, 41.75]],\n" +       // Wrong parameter.
-                "  CS[Cartesian, 2],\n" +
-                "    AXIS[“(Y)”, north],\n" +
-                "    AXIS[“(X)”, east]]");
-            fail("Should not have parsed a WKT with wrong projection parameter.");
-        } catch (InvalidGeodeticParameterException e) {
-            final String message = e.getMessage();
-            assertTrue(message, message.contains("Central parallel"));
-        }
-    }
-
-    /**
-     * Convenience method creating a map with only the "{@code name"} property.
-     * This is the only mandatory property for object creation.
-     */
-    private static Map<String,?> name(final String name) {
-        return Collections.singletonMap(IdentifiedObject.NAME_KEY, name);
-    }
-
-    /**
-     * Tests step-by-step the creation of a new projected coordinate reference systems.
-     * This test creates every objects itself and compares with expected WKT 1 after each step.
-     *
-     * <p>Note that practical applications may use existing constants declared in the
-     * {@link CommonCRS} class instead of creating everything like this test does.</p>
-     *
-     * @throws FactoryException if the creation of a geodetic component failed.
-     *
-     * @since 0.7
-     */
-    @Test
-    public void testStepByStepCreation() throws FactoryException {
-        /*
-         * List of all objects to be created in this test.
-         */
-        final Unit<Length>         linearUnit;
-        final Unit<Angle>          angularUnit;
-        final Ellipsoid            ellipsoid;
-        final PrimeMeridian        meridian;
-        final GeodeticDatum        datum;
-        final CoordinateSystemAxis longitude, latitude, easting, northing;
-        final EllipsoidalCS        geographicCS;
-        final GeographicCRS        geographicCRS;
-        final OperationMethod      method;
-        final ParameterValueGroup  parameters;
-        final Conversion           projection;
-        final CartesianCS          projectedCS;
-        final ProjectedCRS         projectedCRS;
-        /*
-         * Prime meridian
-         */
-        angularUnit = Units.DEGREE;
-        meridian = datumFactory.createPrimeMeridian(name("Greenwich"), 0, angularUnit);
-        assertWktEquals(Convention.WKT1,
-                "PRIMEM[“Greenwich”, 0.0]", meridian);
-        /*
-         * Ellipsoid
-         */
-        linearUnit = Units.METRE;
-        ellipsoid = datumFactory.createEllipsoid(name("Airy1830"), 6377563.396, 6356256.910, linearUnit);
-        assertWktEquals(Convention.WKT1,
-                "SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]", ellipsoid);
-        /*
-         * Geodetic datum
-         */
-        datum = datumFactory.createGeodeticDatum(name("Airy1830"), ellipsoid, meridian);
-        assertWktEquals(Convention.WKT1,
-                "DATUM[“Airy1830”,\n" +
-                "  SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]]", datum);
-        /*
-         * Base coordinate reference system
-         */
-        longitude     =  csFactory.createCoordinateSystemAxis(name("Longitude"), "long", AxisDirection.EAST,  angularUnit);
-        latitude      =  csFactory.createCoordinateSystemAxis(name("Latitude"),  "lat",  AxisDirection.NORTH, angularUnit);
-        geographicCS  =  csFactory.createEllipsoidalCS(name("Ellipsoidal"), longitude, latitude);
-        geographicCRS = crsFactory.createGeographicCRS(name("Airy1830"), datum, geographicCS);
-        assertWktEquals(Convention.WKT1,
-                "GEOGCS[“Airy1830”,\n" +
-                "  DATUM[“Airy1830”,\n" +
-                "    SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]],\n" +
-                "    PRIMEM[“Greenwich”, 0.0],\n" +
-                "  UNIT[“degree”, 0.017453292519943295],\n" +
-                "  AXIS[“Longitude”, EAST],\n" +
-                "  AXIS[“Latitude”, NORTH]]", geographicCRS);
-        /*
-         * Defining conversion
-         */
-        method = copFactory.getOperationMethod("Transverse_Mercator");
-        parameters = method.getParameters().createValue();
-        parameters.parameter("semi_major")        .setValue(ellipsoid.getSemiMajorAxis());
-        parameters.parameter("semi_minor")        .setValue(ellipsoid.getSemiMinorAxis());
-        parameters.parameter("central_meridian")  .setValue(     49);
-        parameters.parameter("latitude_of_origin").setValue(     -2);
-        parameters.parameter("false_easting")     .setValue( 400000);
-        parameters.parameter("false_northing")    .setValue(-100000);
-        projection = new DefaultConversion(name("GBN grid"), method, null, parameters);
-        /*
-         * Projected coordinate reference system
-         */
-        easting      =  csFactory.createCoordinateSystemAxis(name("Easting"),  "x", AxisDirection.EAST,  linearUnit);
-        northing     =  csFactory.createCoordinateSystemAxis(name("Northing"), "y", AxisDirection.NORTH, linearUnit);
-        projectedCS  =  csFactory.createCartesianCS(name("Cartesian"), easting, northing);
-        projectedCRS = crsFactory.createProjectedCRS(name("Great_Britian_National_Grid"), geographicCRS, projection, projectedCS);
-        assertWktEquals(Convention.WKT1,
-                "PROJCS[“Great_Britian_National_Grid”,\n" +
-                "  GEOGCS[“Airy1830”,\n" +
-                "    DATUM[“Airy1830”,\n" +
-                "      SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]],\n" +
-                "      PRIMEM[“Greenwich”, 0.0],\n" +
-                "    UNIT[“degree”, 0.017453292519943295],\n" +
-                "    AXIS[“Longitude”, EAST],\n" +
-                "    AXIS[“Latitude”, NORTH]],\n" +
-                "  PROJECTION[“Transverse_Mercator”, AUTHORITY[“EPSG”, “9807”]],\n" +
-                "  PARAMETER[“latitude_of_origin”, -2.0],\n" +
-                "  PARAMETER[“central_meridian”, 49.0],\n" +
-                "  PARAMETER[“scale_factor”, 1.0],\n" +
-                "  PARAMETER[“false_easting”, 400000.0],\n" +
-                "  PARAMETER[“false_northing”, -100000.0],\n" +
-                "  UNIT[“metre”, 1],\n" +
-                "  AXIS[“Easting”, EAST],\n" +
-                "  AXIS[“Northing”, NORTH]]", projectedCRS);
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
index 63f7e43..376a510 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
@@ -84,7 +84,6 @@
  * @module
  */
 @DependsOn({
-    org.apache.sis.referencing.factory.GeodeticObjectFactoryTest.class,
     org.apache.sis.referencing.factory.AuthorityFactoryProxyTest.class,
     org.apache.sis.referencing.factory.IdentifiedObjectFinderTest.class
 })
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
index 1cbc791..e43f09e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
@@ -121,8 +121,7 @@
      */
     private static InstallationScriptProvider getScripts() throws IOException {
         final InstallationScriptProvider scripts = new InstallationScriptProvider.Default(null);
-        assumeTrue("EPSG scripts not found in Databases/ExternalSources directory.",
-                scripts.getAuthorities().contains(Constants.EPSG));
+        assumeTrue(scripts.getAuthorities().contains(Constants.EPSG));
         return scripts;
     }
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/epsg/package.html b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/epsg/package.html
index 3663c1f..bb1fc62 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/epsg/package.html
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/epsg/package.html
@@ -131,7 +131,7 @@
 mvn clean install
 export CLASSPATH=~/.m2/repository/org/apache/derby/derby/10.14.2.0/derby-10.14.2.0.jar
 export CLASSPATH=$PWD/core/sis-metadata/target/test-classes:$CLASSPATH
-export CLASSPATH=$PWD/target/binaries/sis-referencing-1.x-SNAPSHOT.jar:$CLASSPATH
+export CLASSPATH=$PWD/target/binaries/sis-referencing-1.3-SNAPSHOT.jar:$CLASSPATH
 export CLASSPATH=$PWD/core/sis-metadata/target/test-classes:$CLASSPATH
 export CLASSPATH=$PWD/core/sis-referencing/target/test-classes:$CLASSPATH
 cd <i>&lt;path to local copy of <a href="http://svn.apache.org/repos/asf/sis/data/non-free/">http://svn.apache.org/repos/asf/sis/data/non-free/</a>&gt;</i>
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/geoapi/AuthorityFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/geoapi/AuthorityFactoryTest.java
deleted file mode 100644
index 25ab407..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/geoapi/AuthorityFactoryTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.referencing.geoapi;
-
-import org.opengis.util.FactoryException;
-import org.apache.sis.referencing.CRS;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.Ignore;
-
-
-/**
- * Runs the suite of transformation tests provided in the GeoAPI project.
- * The test suite uses the authority factory instance registered in {@link CRS}.
- * Some (not all) of those tests require the EPSG geodetic database to be installed.
- * If that database is not available, tests that can not be executed will be automatically skipped.
- *
- * @author  Cédric Briançon (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- */
-@RunWith(JUnit4.class)
-public final strictfp class AuthorityFactoryTest extends org.opengis.test.referencing.AuthorityFactoryTest {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     *
-     * @throws FactoryException if no factory can be returned for the given authority.
-     */
-    public AuthorityFactoryTest() throws FactoryException {
-        super(CRS.getAuthorityFactory(null), null, null);
-    }
-
-    /**
-     * Skips for now the <cite>Krovak</cite> projection.
-     */
-    @Override
-    @Ignore("Projection not yet implemented")
-    public void testEPSG_2065() {
-    }
-
-    /**
-     * Skips for now the <cite>Lambert Azimuthal Equal Area</cite> projection.
-     */
-    @Override
-    @Ignore("Projection not yet implemented")
-    public void testEPSG_3035() {
-    }
-
-    /**
-     * Skips for now the <cite>Hyperbolic Cassini-Soldner</cite> projection
-     * because projection derivative (Jacobian matrix) is not yet implemented.
-     */
-    @Override
-    @Ignore("Derivative (Jacobian) not yet implemented")
-    public void testEPSG_3139() {
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/geoapi/ParameterizedTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/geoapi/ParameterizedTransformTest.java
deleted file mode 100644
index 8b7016a..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/geoapi/ParameterizedTransformTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.referencing.geoapi;
-
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.MathTransform2D;
-import org.opengis.referencing.operation.MathTransformFactory;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.After;
-import org.junit.Test;
-
-import static org.opengis.test.Assert.*;
-
-
-/**
- * Runs a suite of tests provided in the GeoAPI project.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- */
-@RunWith(JUnit4.class)
-public final strictfp class ParameterizedTransformTest extends org.opengis.test.referencing.ParameterizedTransformTest {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public ParameterizedTransformTest() {
-        super(DefaultFactories.forBuildin(MathTransformFactory.class));
-    }
-
-    /**
-     * Every map projections shall be instances of {@link MathTransform2D}.
-     * Note that some tests inherited from the parent class are not about
-     * map projections.
-     */
-    @After
-    public void ensureMathTransform2D() {
-        final MathTransform tr = transform;
-        if (tr != null && tr.getSourceDimensions() == 2 && tr.getTargetDimensions() == 2) {
-            assertInstanceOf("Unexpected implementation.", MathTransform2D.class, tr);
-        }
-    }
-
-    /**
-     * Disables the derivative (Jacobian) tests because not yet implemented.
-     *
-     * @throws FactoryException if the math transform can not be created.
-     * @throws TransformException if the example point can not be transformed.
-     */
-    @Test
-    @Override
-    public void testModifiedAzimuthalEquidistant() throws FactoryException, TransformException {
-        isDerivativeSupported = false;
-        super.testModifiedAzimuthalEquidistant();
-    }
-
-    /**
-     * Disables the derivative (Jacobian) tests because not yet implemented.
-     *
-     * @throws FactoryException if the math transform can not be created.
-     * @throws TransformException if the example point can not be transformed.
-     */
-    @Test
-    @Override
-    public void testHyperbolicCassiniSoldner() throws FactoryException, TransformException {
-        isDerivativeSupported = false;
-        super.testHyperbolicCassiniSoldner();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
index 64c1a70..c3cb134 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
@@ -65,7 +65,7 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.Assert;
+import org.apache.sis.test.Assert;
 import org.junit.BeforeClass;
 import org.junit.AfterClass;
 import org.junit.Test;
@@ -308,6 +308,7 @@
         if (targetCRS.getCoordinateSystem().getDimension() == 2) {
             target = TestUtilities.dropLastDimensions(target, 3, 2);
         }
+        tolerance = zTolerance; // Because GeoAPI 3.0 does not distinguish z axis from other axes (fixed in GeoAPI 3.1).
         verifyTransform(source, target);
         validate();
     }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java
index 3f6d6b1..1708431 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java
@@ -18,10 +18,10 @@
 
 import java.util.List;
 import java.text.ParseException;
-import org.opengis.metadata.Identifier;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.SingleOperation;
@@ -114,7 +114,7 @@
      */
     public CoordinateOperationRegistryTest() throws FactoryException {
         crsFactory = CRS.getAuthorityFactory("EPSG");
-        assumeTrue("EPSG factory required.", crsFactory instanceof CoordinateOperationAuthorityFactory);
+        assumeTrue(crsFactory instanceof CoordinateOperationAuthorityFactory);
         registry = new CoordinateOperationRegistry((CoordinateOperationAuthorityFactory) crsFactory, factory, null);
     }
 
@@ -290,6 +290,7 @@
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
         λDimension = new int[] {1};
+        tolerance  = zTolerance; // Because GeoAPI 3.0 does not distinguish z axis from other axes (fixed in GeoAPI 3.1).
         verifyTransform(new double[] {54.271680278,  0.098269657, 20.00},      // in grads east of Paris
                         new double[] {48.844443528,  2.424952028, 63.15});     // in degrees east of Greenwich
         validate();
@@ -325,6 +326,7 @@
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
         λDimension = new int[] {1};
+        tolerance  = zTolerance; // Because GeoAPI 3.0 does not distinguish z axis from other axes (fixed in GeoAPI 3.1).
         verifyTransform(new double[] {0.088442691, 48.844512250, 20.00},      // in degrees east of Paris
                         new double[] {2.424952028, 48.844443528, 63.15});     // in degrees east of Greenwich
         validate();
@@ -380,7 +382,7 @@
     private static void assertEpsgNameWithoutIdentifierEqual(final String name, final IdentifiedObject object) {
         assertNotNull(name, object);
         assertEquals("name", name, object.getName().getCode());
-        for (final Identifier id : object.getIdentifiers()) {
+        for (final ReferenceIdentifier id : object.getIdentifiers()) {
             assertFalse("EPSG identifier not allowed for modified objects.", "EPSG".equalsIgnoreCase(id.getCodeSpace()));
         }
     }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java
index ef0aa2f..cdb1ec0 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java
@@ -21,6 +21,7 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.operation.OperationMethod;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.util.ComparisonMode;
@@ -65,7 +66,7 @@
     {
         final Map<String,Object> properties = new HashMap<>(8);
         assertNull(properties.put(OperationMethod.NAME_KEY, method));
-        assertNull(properties.put(Identifier.CODESPACE_KEY, "EPSG"));
+        assertNull(properties.put(ReferenceIdentifier.CODESPACE_KEY, "EPSG"));
         assertNull(properties.put(Identifier.AUTHORITY_KEY, Citations.EPSG));
         /*
          * The parameter group for a Mercator projection is actually not empty, but it is not the purpose of
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java
index aca3566..84c5141 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java
@@ -28,7 +28,7 @@
 import org.junit.Test;
 
 import static org.apache.sis.test.TestUtilities.getSingleton;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearizerTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearizerTest.java
index 9bb6553..65ac6ff 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearizerTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearizerTest.java
@@ -63,7 +63,7 @@
 
         // Linear approximation by Least Square Root method.
         final LinearTransform linear = LinearTransformBuilder.approximate(transform, new Envelope2D(null, 0, 0, 3, 5));
-        org.opengis.test.Assert.assertMatrixEquals("linear",
+        org.apache.sis.test.Assert.assertMatrixEquals("linear",
                 new Matrix3(111319, 0,   0,
                             0, 110662, -62,
                             0, 0, 1), linear.getMatrix(), 0.5);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
index b709276..41c3e0c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
@@ -24,7 +24,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
index 3632436..a8c6208 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
@@ -20,7 +20,7 @@
 import org.junit.Test;
 
 import static java.lang.Double.NaN;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 import static org.apache.sis.referencing.operation.matrix.Matrix4.SIZE;
 
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java
index 48c3172..09710a6 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java
@@ -23,7 +23,7 @@
 import org.junit.Test;
 
 import static java.lang.Double.NaN;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
index 9c38854..d8f7afb 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
@@ -24,7 +24,6 @@
 import static java.lang.Double.NaN;
 
 // Test dependencies
-import org.opengis.test.ToleranceModifier;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestUtilities;
@@ -74,7 +73,6 @@
 
         final double delta = toRadians(100.0 / 60) / 1852;                  // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
-        toleranceModifier = ToleranceModifier.PROJECTION;
         tolerance = Formulas.LINEAR_TOLERANCE;
         final AlbersEqualArea kernel = (AlbersEqualArea) getKernel();
         assertTrue("isSpherical", isSpherical(kernel));
@@ -123,7 +121,6 @@
 
         final double delta = toRadians(100.0 / 60) / 1852;                  // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
-        toleranceModifier = ToleranceModifier.PROJECTION;
         tolerance = Formulas.LINEAR_TOLERANCE;
         final AlbersEqualArea kernel = (AlbersEqualArea) getKernel();
         assertFalse("isSpherical", isSpherical(kernel));
@@ -163,7 +160,6 @@
     @Test
     @DependsOnMethod("testEllipse")
     public void compareWithPROJ() throws FactoryException, TransformException {
-        toleranceModifier = ToleranceModifier.PROJECTION;
         tolerance = Formulas.LINEAR_TOLERANCE;
 
         // Spherical case
@@ -215,7 +211,6 @@
                 0);         // False northing
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         verifyTransform(new double[] {0,        0,
                                       0,      +90,
                                       0,      -90},
@@ -246,7 +241,6 @@
                 200);       // False northing
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double delta = toRadians(100.0 / 60) / 1852;      // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
         verifyInDomain(new double[] {-40, 10},                  // Minimal input coordinate values
@@ -292,7 +286,6 @@
                 NaN);       // False northing (none)
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         /*
          * Skip inverse transform because the 176.003° become -183.997°. It is not the purpose
          * of this test to verify longitude wraparound in inverse projection (we do not expect
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CassiniSoldnerTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CassiniSoldnerTest.java
index 4c3e600..db1e47a 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CassiniSoldnerTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CassiniSoldnerTest.java
@@ -184,32 +184,4 @@
         verifyDerivative(toRadians(+3), toRadians(-10));
         verifyDerivative(toRadians(-4), toRadians(+10));
     }
-
-    /**
-     * Tests the <cite>"Cassini-Soldner"</cite> (EPSG:9806) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testCassiniSoldner()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(method(false)).testCassiniSoldner();
-    }
-
-    /**
-     * Tests the <cite>"Hyperbolic Cassini-Soldner"</cite> (EPSG:9833) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testHyperbolicCassiniSoldner()
-     */
-    @Test
-    public void runGeoapiHyperbolicTest() throws FactoryException, TransformException {
-        createGeoApiTestNoDerivatives(method(true)).testHyperbolicCassiniSoldner();
-    }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
index 96a770d..adf1795 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
@@ -194,13 +194,6 @@
                 return projection.dy_dφ(sinφ, cos(φ)) * transform(φ);
             }
         };
-        isInverseTransformSupported = false;
-        derivativeDeltas = new double[] {2E-8};
-        tolerance = 1E-7;
-        verifyInDomain(new double[] {-89 * (PI/180)},                  // Minimal value to test.
-                       new double[] {+89 * (PI/180)},                  // Maximal value to test.
-                       new int[]    {100},                             // Number of points to test.
-                       TestUtilities.createRandomNumberGenerator());
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
index 9d34fc0..f26ef6f 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
@@ -18,7 +18,6 @@
 
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.test.ToleranceModifier;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.referencing.provider.LambertCylindricalEqualArea;
 import org.apache.sis.internal.referencing.provider.LambertCylindricalEqualAreaSpherical;
@@ -72,7 +71,6 @@
                 0,          // False easting
                 0);         // False northing
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double λ = 2;
         final double φ = 1;
         final double x = 222638.98;             // Test point from PROJ library.
@@ -102,7 +100,6 @@
                 0,          // False easting
                 0);         // False northing
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double λ = 2;
         final double φ = 1;
         final double x = 222390.10;             // Anti-regression values (not from an external source).
@@ -135,7 +132,6 @@
                 0,          // False easting
                 0);         // False northing
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double λ = 2;
         final double φ = 1;
         final double x = 222390.10;             // Anti-regression values (not from an external source).
@@ -166,7 +162,6 @@
                 200);       // False northing
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double delta = toRadians(100.0 / 60) / 1852;      // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
         verifyInDomain(CoordinateDomain.GEOGRAPHIC_SAFE, 0);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertAzimuthalEqualAreaTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertAzimuthalEqualAreaTest.java
index 72427c2..3c46896 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertAzimuthalEqualAreaTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertAzimuthalEqualAreaTest.java
@@ -341,17 +341,4 @@
         createProjection(false, 8, false);
         verifyDerivative(toRadians(-6), toRadians(2));
     }
-
-    /**
-     * Runs the test defined in the GeoAPI-conformance module.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertAzimuthalEqualArea()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(provider(true)).testLambertAzimuthalEqualArea();
-    }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
index 0edb4c7..059dfd3 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
@@ -26,7 +26,6 @@
 import org.apache.sis.internal.referencing.provider.LambertConformal2SP;
 import org.apache.sis.internal.referencing.provider.LambertConformalWest;
 import org.apache.sis.internal.referencing.provider.LambertConformalBelgium;
-import org.apache.sis.internal.referencing.provider.LambertConformalMichigan;
 import org.apache.sis.referencing.operation.transform.CoordinateDomain;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.internal.util.DoubleDouble;
@@ -39,6 +38,10 @@
 import static java.lang.Double.*;
 import static org.apache.sis.test.Assert.*;
 
+// Branch-specific imports
+import static org.junit.Assume.assumeTrue;
+import static org.apache.sis.test.Assert.PENDING_NEXT_GEOAPI_RELEASE;
+
 
 /**
  * Tests the {@link LambertConicConformal} class. We test using various values of the latitude of origin.
@@ -183,8 +186,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformal1SP()
      */
     @Test
     @DependsOnMethod({"testSpecialLatitudes", "testDerivative"})
@@ -198,8 +199,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformal1SP()
      */
     @Test
     @DependsOnMethod("testLambertConicConformal1SP")
@@ -213,8 +212,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformal1SP()
      */
     @Test
     @DependsOnMethod({"testLambertConicConformal2SP", "verifyBelgeConstant"})
@@ -228,13 +225,11 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformalMichigan()
      */
     @Test
     @DependsOnMethod("testLambertConicConformal2SP")
     public void testLambertConicConformalMichigan() throws FactoryException, TransformException {
-        createGeoApiTest(new LambertConformalMichigan()).testLambertConicConformalMichigan();
+        assumeTrue(PENDING_NEXT_GEOAPI_RELEASE);   // Test not available in GeoAPI 3.0
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
index f45342d..03b910f 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
@@ -21,7 +21,6 @@
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.test.referencing.ParameterizedTransformTest;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.referencing.provider.MapProjection;
@@ -81,24 +80,8 @@
      * @param  provider  the provider of the projection to test.
      * @return the GeoAPI test class using the given provider.
      */
-    static ParameterizedTransformTest createGeoApiTest(final MapProjection provider) {
-        return new ParameterizedTransformTest(new MathTransformFactoryMock(provider));
-    }
-
-    /**
-     * Instantiates the object to use for running GeoAPI test without derivative tests.
-     *
-     * @param  provider  the provider of the projection to test.
-     * @return the GeoAPI test class using the given provider.
-     */
-    static ParameterizedTransformTest createGeoApiTestNoDerivatives(final MapProjection provider) {
-        final class Tester extends ParameterizedTransformTest {
-            Tester(final MapProjection provider) {
-                super(new MathTransformFactoryMock(provider));
-                isDerivativeSupported = false;
-            }
-        }
-        return new Tester(provider);
+    static ParameterizedTransformTestMock createGeoApiTest(final MapProjection provider) {
+        return new ParameterizedTransformTestMock(new MathTransformFactoryMock(provider));
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
index 15d09bd..0fdab7a 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
@@ -23,11 +23,9 @@
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.referencing.provider.Mercator1SP;
 import org.apache.sis.internal.referencing.provider.Mercator2SP;
-import org.apache.sis.internal.referencing.provider.MercatorSpherical;
 import org.apache.sis.internal.referencing.provider.MercatorAuxiliarySphere;
 import org.apache.sis.internal.referencing.provider.PseudoMercator;
 import org.apache.sis.internal.referencing.provider.MillerCylindrical;
-import org.apache.sis.internal.referencing.provider.RegionalMercator;
 import org.apache.sis.referencing.operation.transform.CoordinateDomain;
 import org.apache.sis.referencing.operation.transform.MathTransformFactoryMock;
 import org.apache.sis.parameter.Parameters;
@@ -40,6 +38,10 @@
 import static org.opengis.test.Assert.*;
 import static org.apache.sis.referencing.operation.projection.ConformalProjectionTest.LN_INFINITY;
 
+// Branch-specific imports
+import static org.junit.Assume.assumeTrue;
+import static org.apache.sis.test.Assert.PENDING_NEXT_GEOAPI_RELEASE;
+
 
 /**
  * Tests the {@link Mercator} projection.
@@ -184,8 +186,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercator1SP()
      */
     @Test
     @DependsOnMethod({"testSpecialLatitudes", "testDerivative"})
@@ -199,8 +199,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercator2SP()
      */
     @Test
     @DependsOnMethod("testMercator1SP")
@@ -214,13 +212,11 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercatorVariantC()
      */
     @Test
     @DependsOnMethod("testMercator2SP")
     public void testRegionalMercator() throws FactoryException, TransformException {
-        createGeoApiTest(new RegionalMercator()).testMercatorVariantC();
+        assumeTrue(PENDING_NEXT_GEOAPI_RELEASE);   // Test not available in GeoAPI 3.0
     }
 
     /**
@@ -229,13 +225,11 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercatorSpherical()
      */
     @Test
     @DependsOnMethod("testMercator1SP")
     public void testMercatorSpherical() throws FactoryException, TransformException {
-        createGeoApiTest(new MercatorSpherical()).testMercatorSpherical();
+        assumeTrue(PENDING_NEXT_GEOAPI_RELEASE);   // Test not available in GeoAPI 3.0
     }
 
     /**
@@ -244,8 +238,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPseudoMercator()
      */
     @Test
     @DependsOnMethod("testMercatorSpherical")
@@ -259,8 +251,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMiller()
      */
     @Test
     @DependsOnMethod("testMercator1SP")
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java
index e090f30..ddca1c2 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java
@@ -70,20 +70,6 @@
     }
 
     /**
-     * Tests the <cite>"Modified Azimuthal Equidistant"</cite> (EPSG:9832) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testModifiedAzimuthalEquidistant()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTestNoDerivatives(method()).testModifiedAzimuthalEquidistant();
-    }
-
-    /**
      * Not yet supported.
      */
     @Test
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
index 5312864..c3b2486 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
@@ -16,7 +16,7 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import org.opengis.test.referencing.TransformTestCase;
+import org.apache.sis.referencing.operation.transform.TransformTestCase;
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
index fc0f300..0633419 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
@@ -16,14 +16,10 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import org.opengis.test.ToleranceModifier;
-import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.referencing.operation.MathTransformFactory;
 import org.apache.sis.internal.referencing.provider.ObliqueMercatorCenter;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.test.DependsOn;
@@ -37,8 +33,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @author  Emmanuel Giasson (Thales)
- * @version 1.2
+ * @version 1.0
  * @since   1.0
  * @module
  */
@@ -82,60 +77,4 @@
         verifyDerivative(toRadians(15), toRadians(40));
         verifyDerivative(toRadians(10), toRadians(60));
     }
-
-    /**
-     * Tests with an azimuth of 90°.
-     *
-     * @throws TransformException if an error occurred while converting a point.
-     */
-    @Test
-    public void testAzimuth90() throws TransformException {
-        tolerance = 1E-9;
-        transform = create(10, 20, 90);
-        validate();
-
-        final double delta = toRadians(100.0 / 60) / 1852;      // Approximately 100 metres.
-        derivativeDeltas = new double[] {delta, delta};
-        verifyInverse(toRadians(15), toRadians(25));
-    }
-
-    /**
-     * Tests with a latitude close to 90°.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while converting a point.
-     *
-     * <a href="https://issues.apache.org/jira/browse/SIS-532">SIS-532</a>
-     */
-    @Test
-    public void testPole() throws TransformException, FactoryException {
-        tolerance = 0.01;
-        transform = create(179.8, 89.8, -174).createMapProjection(DefaultFactories.forBuildin(MathTransformFactory.class));
-        transform = transform.inverse();
-        validate();
-        /*
-         * The projection of (180, 90) with SIS 1.1 is (+0.004715980030596256, 22338.795490272343).
-         * Empirical cordinates shifted by 0.01 meter: (-0.005463426921067797, 22338.792057282844).
-         * With those shifted coordinated, Apache SIS 1.1 was used to compute φ = NaN because the
-         * U′ value in `ObliqueMercator.inverseTransform(…)` was slightly greater than 1.
-         */
-        isInverseTransformSupported = false;
-        toleranceModifier = ToleranceModifier.GEOGRAPHIC;
-        verifyTransform(new double[] {-0.005464, 22338.792057},
-                        new double[] {300, 90});
-    }
-
-    /**
-     * Tests the <cite>"Hotine Oblique Mercator (variant B)"</cite> (EPSG:9815) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testHotineObliqueMercator()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(new ObliqueMercatorCenter()).testHotineObliqueMercator();
-    }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java
index f705115..1eaa2da 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java
@@ -24,7 +24,6 @@
 import org.opengis.util.FactoryException;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.referencing.operation.transform.ContextualParameters;
-import org.apache.sis.referencing.operation.matrix.Matrix2;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.measure.Units;
@@ -33,7 +32,7 @@
 import org.junit.Test;
 
 import static java.lang.StrictMath.*;
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -223,21 +222,6 @@
     }
 
     /**
-     * Tests the <cite>Oblique Stereographic</cite> case (EPSG:9809).
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testObliqueStereographic()
-     */
-    @Test
-    @DependsOnMethod({"testTransform", "testInverseTransform"})
-    public void testObliqueStereographic() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.internal.referencing.provider.ObliqueStereographic()).testObliqueStereographic();
-    }
-
-    /**
      * Tests consistency between forward and inverse projection using a point that was known to fail.
      *
      * @throws FactoryException if an error occurred while creating the map projection.
@@ -255,7 +239,7 @@
         p.parameter("Longitude of natural origin").setValue(-70, Units.DEGREE);
         createCompleteTransform(op, p);
         tolerance = Formulas.ANGULAR_TOLERANCE;
-        verifyInverse(30, 45);
+        verifyInverse(new double[] {30, 45});
     }
 
     /**
@@ -370,9 +354,7 @@
         final Matrix derivative = spherical.transform(srcPts, 0, null, 0, true);
 
         tolerance = 1E-12;
-        assertMatrixEquals("Spherical derivative", reference, derivative,
-                new Matrix2(tolerance, tolerance,
-                            tolerance, tolerance));
+        assertMatrixEquals("Spherical derivative", reference, derivative, tolerance);
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/OrthographicTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/OrthographicTest.java
index 434d55b..42fc7c5 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/OrthographicTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/OrthographicTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.operation.transform.CoordinateDomain;
 import org.apache.sis.internal.referencing.provider.MapProjection;
@@ -145,18 +144,4 @@
         verifyInDomain(CoordinateDomain.GEOGRAPHIC_RADIANS_SOUTH, 753524735);
         verifyDerivative(toRadians(5), toRadians(-85));
     }
-
-    /**
-     * Tests the <cite>"Orthographic"</cite> (EPSG:9840) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testOrthographic()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.internal.referencing.provider.Orthographic()).testOrthographic();
-    }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ParameterizedTransformTestMock.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ParameterizedTransformTestMock.java
new file mode 100644
index 0000000..1588401
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ParameterizedTransformTestMock.java
@@ -0,0 +1,80 @@
+/*
+ * 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.referencing.operation.projection;
+
+import org.opengis.referencing.operation.MathTransformFactory;
+
+import static org.junit.Assume.*;
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+ * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.5
+ * @version 0.6
+ * @module
+ */
+final class ParameterizedTransformTestMock {
+    ParameterizedTransformTestMock(MathTransformFactory factory) {
+        // See GeoAPI 3.1 for real construction.
+    }
+
+    public void testMercator1SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testMercator2SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testPseudoMercator() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testMiller() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testLambertConicConformal1SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testLambertConicConformal2SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testLambertConicConformalBelgium() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java
index 4d99553..0367236 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java
@@ -120,12 +120,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicA()
      */
     @Test
     public void testPolarStereographicA() throws FactoryException, TransformException {
-        createGeoApiTest(new PolarStereographicA()).testPolarStereographicA();
+        new PolarStereographicA();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -134,12 +132,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicB()
      */
     @Test
     public void testPolarStereographicB() throws FactoryException, TransformException {
-        createGeoApiTest(new PolarStereographicB()).testPolarStereographicB();
+        new PolarStereographicB();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -148,12 +144,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicC()
      */
     @Test
     public void testPolarStereographicC() throws FactoryException, TransformException {
-        createGeoApiTest(new PolarStereographicC()).testPolarStereographicC();
+        new PolarStereographicC();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java
index 9f1cbe9..86ed32b 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java
@@ -140,16 +140,4 @@
         verifyDerivative( -56, 50);
         verifyDerivative( -20, 47);
     }
-
-    /**
-     * Runs the test defined in the GeoAPI-conformance module.
-     *
-     * @throws FactoryException   if the transform can not be created.
-     * @throws TransformException if an error occurred while projecting a point.
-     */
-    @Test
-    @DependsOnMethod("testEllipsoidal")
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.internal.referencing.provider.Polyconic()).testPolyconic();
-    }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
index 75aa143..0969170 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
@@ -87,16 +87,7 @@
          * coordinate values. The transform results are between 0 and 1, while the inverse transform results
          * are between -90° and 90°, which is an increase in magnitude close to ×100.
          */
-        toleranceModifier = (tolerance, coordinate, mode) -> {
-            switch (mode) {
-                case INVERSE_TRANSFORM: {
-                    for (int i=0; i<tolerance.length; i++) {
-                        tolerance[i] *= 50;
-                    }
-                    break;
-                }
-            }
-        };
+        tolerance *= 50;    // Finer tolerance setting require GeoAPI 3.1.
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
index d512b4f..e31de7b 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
@@ -36,7 +36,6 @@
 import static java.lang.StrictMath.toRadians;
 import static org.apache.sis.test.Assert.*;
 import static org.apache.sis.internal.referencing.provider.TransverseMercator.LATITUDE_OF_ORIGIN;
-import org.opengis.test.CalculationType;
 
 
 /**
@@ -109,12 +108,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testTransverseMercator()
      */
     @Test
     public void testTransverseMercator() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.internal.referencing.provider.TransverseMercator()).testTransverseMercator();
+        new org.apache.sis.internal.referencing.provider.TransverseMercator();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -123,13 +120,11 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testTransverseMercatorSouthOrientated()
      */
     @Test
     @DependsOnMethod("testTransverseMercator")
     public void testTransverseMercatorSouthOrientated() throws FactoryException, TransformException {
-        createGeoApiTest(new TransverseMercatorSouth()).testTransverseMercatorSouthOrientated();
+        new TransverseMercatorSouth();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -269,7 +264,7 @@
                     else if (longitude <= 66) tolerance = 0.1;
                     else                      tolerance = 0.7;
                     transform.transform(source, 0, source, 0, 1);
-                    assertCoordinateEquals(line, target, source, reader.getLineNumber(), CalculationType.DIRECT_TRANSFORM);
+                    assertCoordinateEquals(line, target, source, reader.getLineNumber(), false);
                 }
             }
         }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
index eafcca3..f2f35dd 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.referencing.operation.transform;
 
-import org.opengis.test.CalculationType;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
@@ -61,8 +60,6 @@
      *
      * @throws FactoryException if an error occurred while creating a transform step.
      * @throws TransformException if a transformation failed.
-     *
-     * @see MolodenskyTransformTest#compareWithGeocentricTranslation()
      */
     @Test
     public void compareWithReferenceImplementation() throws FactoryException, TransformException {
@@ -77,7 +74,7 @@
         for (int i=0; i<sources.length; i+=2) {
             transform.transform(sources, i, targets,  0, 1);        // Use the overridden method.
             transform.transform(sources, i, expected, 0, false);    // Use the MolodenskyTransform method.
-            assertCoordinateEquals("transform", expected, targets, 0, CalculationType.DIRECT_TRANSFORM);
+            assertCoordinateEquals("transform", expected, targets, 0, false);
         }
     }
 
@@ -113,7 +110,6 @@
     public void testConsistency() throws FactoryException, TransformException {
         transform = create();
         validate();
-        isDerivativeSupported = false;
         isInverseTransformSupported = false;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, -1941624852762631518L);
         /*
@@ -122,7 +118,6 @@
          * we are not completely sure that there is no bug in derivative formula).
          */
         tolerance *= 10;
-        isDerivativeSupported = true;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 4350796528249596132L);
     }
 
@@ -138,7 +133,6 @@
     public void testSerialization() throws FactoryException, TransformException {
         transform = assertSerializedEquals(create());
         validate();
-        isDerivativeSupported = false;
         isInverseTransformSupported = false;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 3534897149662911157L);
     }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java
index fc20bcd..533503d 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java
@@ -20,7 +20,6 @@
 import org.opengis.referencing.operation.TransformException;
 
 // Test dependencies
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestUtilities;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java
index b914e47..3c4866a 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java
@@ -20,7 +20,6 @@
 import org.opengis.referencing.operation.TransformException;
 
 // Test dependencies
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestUtilities;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
index 51030a0..88f540c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransformTest.java
@@ -27,7 +27,7 @@
 import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
-import org.opengis.test.Assert;
+import org.apache.sis.test.Assert;
 
 import static org.opengis.test.Assert.*;
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
index eac37f4..a124c04 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
@@ -31,7 +31,7 @@
 
 import static java.lang.StrictMath.PI;
 import static java.lang.StrictMath.toRadians;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
index f117804..91dbb00 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
@@ -49,7 +49,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
index e4aa547..104c07e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
@@ -33,7 +33,6 @@
 import static java.lang.StrictMath.toRadians;
 
 // Test dependencies
-import org.opengis.test.ToleranceModifier;
 import org.apache.sis.internal.referencing.provider.GeocentricTranslationTest;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
@@ -116,6 +115,7 @@
         tolerance  = GeocentricTranslationTest.precision(1);        // Required precision for (λ,φ)
         zTolerance = Formulas.LINEAR_TOLERANCE / 2;                 // Required precision for h
         zDimension = new int[] {2};                                 // Dimension of h where to apply zTolerance
+        tolerance  = 1E-4;                                          // Other SIS branches use a stricter threshold.
         verifyTransform(GeocentricTranslationTest.samplePoint(2),   // X = 3771793.968,  Y = 140253.342,  Z = 5124304.349 metres
                         GeocentricTranslationTest.samplePoint(1));  // 53°48'33.820"N, 02°07'46.380"E, 73.00 metres
     }
@@ -135,7 +135,7 @@
         final double delta = toRadians(100.0 / 60) / 1852;          // Approximately 100 metres
         derivativeDeltas  = new double[] {delta, delta, 100};       // (Δλ, Δφ, Δh)
         tolerance         = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
+//      toleranceModifier = ToleranceModifier.PROJECTION;
         createGeodeticConversion(CommonCRS.WGS84.ellipsoid(), true);
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 306954540);
     }
@@ -157,8 +157,8 @@
         final double delta = toRadians(100.0 / 60) / 1852;
         derivativeDeltas  = new double[] {delta, delta, 100};
         tolerance         = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
-        verifyInverse(40, 30, 10000);
+//      toleranceModifier = ToleranceModifier.PROJECTION;
+        verifyInverse(new double[] {40, 30, 10000});
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java
index 5a29fc4..2859fe5 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.referencing.operation.transform;
 
-import java.util.EnumSet;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.TransformException;
 import static java.lang.StrictMath.*;
@@ -27,11 +26,6 @@
 import org.apache.sis.test.DependsOnMethod;
 import static org.apache.sis.test.ReferencingAssert.*;
 
-// Branch-dependent imports
-import org.opengis.test.CalculationType;
-import org.opengis.test.ToleranceModifier;
-import org.opengis.test.ToleranceModifiers;
-
 
 /**
  * Tests the {@link ExponentialTransform1D} class. Note that this is closely related to
@@ -66,9 +60,8 @@
      * Creates a new test case.
      */
     public ExponentialTransform1DTest() {
-        tolerance         = 1E-14;
-        toleranceModifier = ToleranceModifier.RELATIVE;
-        derivativeDeltas  = new double[] {0.001};
+        tolerance         = 1E-5; // Tolerance is much smaller on other branches.
+//      toleranceModifier = ToleranceModifier.RELATIVE; // Not available on GeoAPI 3.0.
     }
 
     /**
@@ -104,7 +97,6 @@
             expected[i] = value;
         }
         verifyTransform(values, expected);
-        verifyDerivative(2.5);                                      // Test at a hard-coded point.
     }
 
     /**
@@ -138,13 +130,6 @@
     private void testAffinePostConcatenation(final double base) throws TransformException {
         transform = MathTransforms.concatenate(ExponentialTransform1D.create(base, SCALE),
                 LinearTransform1D.create(C1, C0));
-        /*
-         * The inverse transforms in this test case have high rounding errors.
-         * Those errors are low for values close to zero, and increase fast for higher values.
-         * We scale the default tolerance (1E-14) by 1E+8, which give us a tolerance of 1E-6.
-         */
-        toleranceModifier = ToleranceModifiers.concatenate(toleranceModifier,
-                ToleranceModifiers.scale(EnumSet.of(CalculationType.INVERSE_TRANSFORM), 1E+8));
         run(ConcatenatedTransformDirect1D.class, base, SCALE, false, true);
     }
 
@@ -154,10 +139,6 @@
     private void testAffineConcatenations(final double base) throws TransformException {
         final LinearTransform1D linear = LinearTransform1D.create(C1, C0);
         transform = MathTransforms.concatenate(linear, ExponentialTransform1D.create(base, SCALE), linear);
-
-        // See testAffinePostConcatenation for an explanation about why we relax tolerance.
-        toleranceModifier = ToleranceModifiers.concatenate(toleranceModifier,
-                ToleranceModifiers.scale(EnumSet.of(CalculationType.INVERSE_TRANSFORM), 1E+8));
         run(ConcatenatedTransformDirect1D.class, base, SCALE, true, true);
     }
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java
index 1920a64..3f59003 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java
@@ -33,8 +33,8 @@
  * instead of the real geocentric translation is verified by the following tests:
  *
  * <ul>
- *   <li>{@link GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()}</li>
- *   <li>{@link MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()}</li>
+ *   <li>{@code GeocentricTranslationTest.testFranceGeocentricInterpolationPoint()}</li>
+ *   <li>{@code MolodenskyTransformTest.testFranceGeocentricInterpolationPoint()}</li>
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
index 4b3ecfc..a6e509c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
@@ -20,7 +20,6 @@
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.DependsOnMethod;
 import org.junit.Test;
 
@@ -36,7 +35,7 @@
  * @since   0.7
  * @module
  */
-public final strictfp class LinearInterpolator1DTest extends TransformTestCase {
+public final strictfp class LinearInterpolator1DTest extends MathTransformTestCase {
     /**
      * The values of the <i>y=f(x)</i> function to test.
      */
@@ -255,7 +254,6 @@
         verifyTransform(new double[] {0,  1, 0.5, -0.5, -1, -2,   3, 3.5,   4,   5},        // Values to transform.
                         new double[] {5, 10, 7.5,  2.5,  0, -5, 250, 325, 400, 550});       // Expected results.
 
-        verifyConsistency(0f, 1f, 0.5f, -0.5f, -1f, -2f, 3f, 3.5f, 4f, 5f);
         verifyDerivative(0.25);     // Interpolation (verified by safety)
         verifyDerivative(-8);       // Extrapolation
         verifyDerivative( 8);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearTransformTest.java
index 20d1aa5..0168262 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearTransformTest.java
@@ -28,6 +28,9 @@
 import org.apache.sis.test.TestRunner;
 import static org.opengis.test.Assert.*;
 
+// Branch-dependent imports
+import org.junit.Ignore;
+
 
 /**
  * Tests various implementation of the {@link LinearTransform} interface by inheriting the tests defined
@@ -69,6 +72,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testIdentity1D() throws FactoryException, TransformException {
         super.testIdentity1D();
         assertInstanceOf("Unexpected implementation.", IdentityTransform1D.class, transform);
@@ -82,6 +86,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testIdentity2D() throws FactoryException, TransformException {
         super.testIdentity2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -95,6 +100,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testIdentity3D() throws FactoryException, TransformException {
         super.testIdentity3D();
         assertInstanceOf("Unexpected implementation.", IdentityTransform.class, transform);
@@ -108,6 +114,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testAxisSwapping2D() throws FactoryException, TransformException {
         super.testAxisSwapping2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -121,6 +128,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testSouthOrientated2D() throws FactoryException, TransformException {
         super.testSouthOrientated2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -134,6 +142,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testTranslatation2D() throws FactoryException, TransformException {
         super.testTranslatation2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -147,6 +156,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testUniformScale2D() throws FactoryException, TransformException {
         super.testUniformScale2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -160,6 +170,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testGenericScale2D() throws FactoryException, TransformException {
         super.testGenericScale2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -173,6 +184,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testRotation2D() throws FactoryException, TransformException {
         super.testRotation2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -186,6 +198,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testGeneral() throws FactoryException, TransformException {
         super.testGeneral();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -199,6 +212,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testDimensionReduction() throws FactoryException, TransformException {
         super.testDimensionReduction();
         assertInstanceOf("Unexpected implementation.", ProjectiveTransform.class, transform);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java
index bd93fc3..b1fcdd8 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java
@@ -26,9 +26,6 @@
 import org.apache.sis.test.DependsOnMethod;
 import static org.apache.sis.test.ReferencingAssert.*;
 
-// Branch-dependent imports
-import org.opengis.test.ToleranceModifier;
-
 
 /**
  * Tests the {@link LogarithmicTransform1D} class. Note that this is closely related to
@@ -55,9 +52,8 @@
      * Creates a new test case.
      */
     public LogarithmicTransform1DTest() {
-        tolerance         = 2E-14;
-        toleranceModifier = ToleranceModifier.RELATIVE;
-        derivativeDeltas  = new double[] {0.001};
+        tolerance         = 1E-5; // Tolerance is much smaller on other branches.
+//      toleranceModifier = ToleranceModifier.RELATIVE; // Not available on GeoAPI 3.0.
     }
 
     /**
@@ -95,7 +91,6 @@
             expected[i] = value;
         }
         verifyTransform(values, expected);
-        verifyDerivative(2.5);                                          // Test at a hard-coded point.
     }
 
     /**
@@ -224,6 +219,5 @@
             expected[i] = ExponentialTransform1DTest.SCALE * pow(10, log(value) / lnBase + C0);
         }
         verifyTransform(values, expected);
-        verifyDerivative(2.5);                                  // Test at a hard-coded point.
     }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
index 37373fa..9ae7a42 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
@@ -111,6 +111,13 @@
 
     /** Default implementation throws an exception. */
     @Override
+    @Deprecated
+    public MathTransform createFromXML(String xml) throws FactoryException {
+        throw new FactoryException(MESSAGE);
+    }
+
+    /** Default implementation throws an exception. */
+    @Override
     public MathTransform createFromWKT(String wkt) throws FactoryException {
         throw new FactoryException(MESSAGE);
     }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
index c7d9487..dd148fd 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
@@ -159,6 +159,18 @@
     /**
      * Unimplemented method.
      *
+     * @param  xml  ignored.
+     * @return never returned.
+     */
+    @Override
+    @Deprecated
+    public MathTransform createFromXML(String xml) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unimplemented method.
+     *
      * @param  wkt  ignored.
      * @return never returned.
      */
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
index 7b949b1..bd13467 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
@@ -19,17 +19,14 @@
 import java.util.Random;
 import java.io.IOException;
 import java.io.UncheckedIOException;
-import org.opengis.util.Factory;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.MathTransform2D;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.geometry.DirectPosition;
 import org.opengis.metadata.Identifier;
 import org.apache.sis.parameter.Parameterized;
-import org.apache.sis.measure.Longitude;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ArraysExt;
@@ -40,14 +37,10 @@
 
 // Test imports
 import org.opengis.test.Validators;
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.ReferencingAssert;
 import static org.opengis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.test.CalculationType;
-
 
 /**
  * Base class for tests of {@link AbstractMathTransform} implementations.
@@ -56,7 +49,6 @@
  * <p>Various assertion methods:</p>
  * <ul>
  *   <li>{@link #assertCoordinateEquals assertCoordinateEquals(…)}  — from GeoAPI</li>
- *   <li>{@link #assertMatrixEquals     assertMatrixEquals(…)}      — from GeoAPI</li>
  *   <li>{@link #assertParameterEquals  assertParameterEquals(…)}   — from Apache SIS</li>
  *   <li>{@link #assertWktEquals        assertWktEquals(…)}         — from Apache SIS</li>
  * </ul>
@@ -113,63 +105,6 @@
      * Creates a new test case.
      */
     protected MathTransformTestCase() {
-        this(new Factory[0]);
-    }
-
-    /**
-     * Creates a new test case which will use the given factories. Those factories will be given to
-     * {@link org.opengis.test.ImplementationDetails#configuration(Factory[])} in order to decide
-     * which tests should be enabled.
-     *
-     * @param factories  the factories to be used by the test.
-     */
-    protected MathTransformTestCase(final Factory... factories) {
-        super(factories);
-        /*
-         * Use 'zTolerance' threshold instead of 'tolerance' when comparing vertical coordinate values.
-         */
-        toleranceModifier = (final double[] tolerance, final DirectPosition coordinate, final CalculationType mode) -> {
-            if (mode != CalculationType.IDENTITY) {
-                final int i = forComparison(zDimension, mode);
-                if (i >= 0 && i < tolerance.length) {
-                    tolerance[i] = zTolerance;
-                }
-            }
-        };
-    }
-
-    /**
-     * Returns the value to use from the {@link #λDimension} or {@link zDimension} for the
-     * given comparison mode, or -1 if none.
-     */
-    @SuppressWarnings("fallthrough")
-    private static int forComparison(final int[] config, final CalculationType mode) {
-        if (config != null) {
-            switch (mode) {
-                case INVERSE_TRANSFORM: if (config.length >= 2) return config[1];       // Intentional fallthrough.
-                case DIRECT_TRANSFORM:  if (config.length >= 1) return config[0];
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Invoked by all {@code assertCoordinateEquals(…)} methods before two positions are compared.
-     * The SIS implementation ensures that longitude values are contained in the ±180° range,
-     * applying 360° shifts if needed.
-     *
-     * @param  expected  the expected coordinate values provided by the test case.
-     * @param  actual    the coordinate values computed by the {@linkplain #transform transform} being tested.
-     * @param  mode      indicates if the coordinates being compared are the result of a direct
-     *                   or inverse transform, or if strict equality is requested.
-     */
-    @Override
-    protected final void normalize(final DirectPosition expected, final DirectPosition actual, final CalculationType mode) {
-        final int i = forComparison(λDimension, mode);
-        if (i >= 0) {
-            expected.setOrdinate(i, Longitude.normalize(expected.getOrdinate(i)));
-            actual  .setOrdinate(i, Longitude.normalize(actual  .getOrdinate(i)));
-        }
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
index 11c199b..8dda6d0 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
@@ -33,7 +33,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
index cd33e59..ad5d08c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
@@ -17,24 +17,18 @@
 package org.apache.sis.referencing.operation.transform;
 
 import java.util.Arrays;
-import java.io.IOException;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.datum.Ellipsoid;
-import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolation;
-import org.apache.sis.internal.referencing.provider.AbridgedMolodensky;
 import org.apache.sis.internal.referencing.provider.Molodensky;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.math.StatisticsFormat;
-import org.apache.sis.math.Statistics;
 
 import static java.lang.StrictMath.*;
-import static org.apache.sis.internal.metadata.ReferencingServices.NAUTICAL_MILE;
 
 // Test dependencies
 import org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolationTest;
@@ -43,18 +37,13 @@
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestUtilities;
-import org.apache.sis.test.TestCase;
-import org.opengis.test.CalculationType;
-import org.opengis.test.ToleranceModifier;
-import org.opengis.test.ToleranceModifiers;
-import org.opengis.test.referencing.ParameterizedTransformTest;
 import org.junit.Test;
 
 import static org.apache.sis.test.Assert.*;
 
 
 /**
- * Tests {@link MolodenskyTransform}. The {@link #compareWithGeocentricTranslation()}
+ * Tests {@link MolodenskyTransform}. The {@code compareWithGeocentricTranslation()}
  * method uses {@link EllipsoidToCentricTransform} as a reference implementation.
  * The errors compared to geocentric translations should not be greater than
  * approximately 1 centimetre.
@@ -85,65 +74,6 @@
     }
 
     /**
-     * Compares the Molodensky (non-abridged) transform with a geocentric translation.
-     * Molodensky is an approximation of geocentric translation, so we test here how good this approximation is.
-     * If {@link TestCase#VERBOSE} is {@code true}, then this method will print error statistics.
-     *
-     * @throws FactoryException if an error occurred while creating a transform step.
-     * @throws TransformException if a transformation failed.
-     * @throws IOException should never happen.
-     *
-     * @see #compareWithGeocentricTranslation()
-     */
-    @SuppressWarnings("fallthrough")
-    private void compareWithGeocentricTranslation(
-            final Ellipsoid source, final Ellipsoid target,
-            final double tX,   final double tY,   final double tZ,
-            final double xmin, final double ymin, final double zmin,
-            final double xmax, final double ymax, final double zmax)
-            throws FactoryException, TransformException, IOException
-    {
-        final MathTransform reference;
-        final MathTransformFactory factory = DefaultFactories.forBuildin(MathTransformFactory.class);
-        transform = MolodenskyTransform.createGeodeticTransformation(factory, source, true, target, true, tX, tY, tZ, false);
-        reference = GeocentricTranslationTest.createDatumShiftForGeographic3D(factory, source, target, tX, tY, tZ);
-        final float[] srcPts = verifyInDomain(
-                new double[] {xmin, ymin, zmin},
-                new double[] {xmax, ymax, zmax},
-                new int[]    {  10,   10,   10},
-                TestUtilities.createRandomNumberGenerator(103627524044558476L));
-        /*
-         * Transform the same input coordinates using Molodensky transform (actual) and using the reference
-         * implementation (expected). If we were asked to print statistics, compute them before to test the
-         * values since the statistics may be a useful information in case of problem.
-         */
-        final double[] actual   = new double[srcPts.length];
-        final double[] expected = new double[srcPts.length];
-        transform.transform(srcPts, 0, actual,   0, srcPts.length / 3);
-        reference.transform(srcPts, 0, expected, 0, srcPts.length / 3);
-        if (TestCase.VERBOSE) {
-            final Statistics[] stats = {
-                new Statistics("|Δλ| (~cm)"),
-                new Statistics("|Δφ| (~cm)"),
-                new Statistics("|Δh| (cm)")
-            };
-            for (int i=0; i<srcPts.length; i++) {
-                double Δ = actual[i] - expected[i];
-                final int j = i % stats.length;
-                switch (j) {
-                    case 0: Δ *= cos(toRadians(expected[i+1]));     // Fall through
-                    case 1: Δ *= 60 * NAUTICAL_MILE; break;         // Approximate conversion to metres
-                }
-                Δ *= 100;   // Conversion to centimetres.
-                stats[j].accept(abs(Δ));
-            }
-            StatisticsFormat.getInstance().format(stats, TestCase.out);
-        }
-        assertCoordinatesEqual("Comparison of Molodensky and geocentric translation", 3,
-                expected, 0, actual, 0, expected.length / 3, CalculationType.DIRECT_TRANSFORM);
-    }
-
-    /**
      * Creates a Molodensky transform for a datum shift from WGS84 to ED50.
      * Tolerance thresholds are also initialized.
      *
@@ -214,6 +144,7 @@
         final double[] sample   = GeocentricTranslationTest.samplePoint(1);
         final double[] expected = GeocentricTranslationTest.samplePoint(5);
         isInverseTransformSupported = false;
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Other SIS branches use a stricter threshold.
         verifyTransform(sample, expected);
         /*
          * When testing the inverse transformation, we need to relax slightly
@@ -243,6 +174,7 @@
         final double[] sample   = GeocentricTranslationTest.samplePoint(1);
         final double[] expected = GeocentricTranslationTest.samplePoint(4);
         isInverseTransformSupported = false;
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Other SIS branches use a stricter threshold.
         verifyTransform(sample, expected);
         /*
          * When testing the inverse transformation, we need to relax slightly
@@ -261,8 +193,6 @@
      *
      * @throws FactoryException if an error occurred while creating the transform.
      * @throws TransformException if transformation of a point failed.
-     *
-     * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()
      */
     @Test
     @DependsOnMethod("testMolodensky")
@@ -279,7 +209,7 @@
          * Code below is a copy-and-paste of GeocentricTranslationTest.testFranceGeocentricInterpolationPoint(),
          * but with the tolerance threshold increased. We do not let the error goes beyond 1 cm however.
          */
-        tolerance = min(Formulas.ANGULAR_TOLERANCE, FranceGeocentricInterpolationTest.ANGULAR_TOLERANCE * 6);
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Other SIS branches use a stricter threshold.
         final double[] source   = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(1), 3);
         final double[] expected = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(2), 3);
         expected[2] = 43.15;  // Anti-regression (this value is not provided in NTG_88 guidance note).
@@ -288,47 +218,6 @@
     }
 
     /**
-     * Compares the Molodensky (non-abridged) transforms with geocentric translations.
-     * Molodensky is an approximation of geocentric translation, so we test here how good this
-     * approximation is. This test performs the comparison for the following transformations:
-     *
-     * <ul>
-     *   <li>Transformation from NTF to RGF93. Those CRS are the source and target of <cite>"France geocentric
-     *       interpolation"</cite> (ESPG:9655). This test allows us to verify the accuracy documented in
-     *       {@link InterpolatedGeocentricTransform}.</li>
-     *   <li>(More areas may be added later).</li>
-     * </ul>
-     *
-     * If {@link TestCase#VERBOSE} is {@code true}, then this method will print error statistics.
-     *
-     * @throws FactoryException if an error occurred while creating a transform step.
-     * @throws TransformException if a transformation failed.
-     * @throws IOException should never happen.
-     *
-     * @see #testFranceGeocentricInterpolationPoint()
-     */
-    @Test
-    @DependsOnMethod("testFranceGeocentricInterpolationPoint")
-    public void compareWithGeocentricTranslation() throws FactoryException, TransformException, IOException {
-        /*
-         * Disable the test for inverse transformations because they are not the purpose of this test.
-         * Errors of inverse transformations are added to the error of forward transformations, which
-         * would force us to double the tolerance threshold.
-         */
-        isInverseTransformSupported = false;
-        tolerance         = 3*Formulas.LINEAR_TOLERANCE; // To be converted in degrees by ToleranceModifier.GEOGRAPHIC
-        zTolerance        = 4*Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifiers.concatenate(ToleranceModifier.GEOGRAPHIC, toleranceModifier);
-        compareWithGeocentricTranslation(HardCodedDatum.NTF.getEllipsoid(),   // Clarke 1880 (IGN)
-                                         CommonCRS.ETRS89.ellipsoid(),        // GRS 1980 ellipsoid
-                                         FranceGeocentricInterpolation.TX,
-                                         FranceGeocentricInterpolation.TY,
-                                         FranceGeocentricInterpolation.TZ,
-                                         -5.5, 41.0, -200,   // Geographic area of GR2DF97A datum shift grid.
-                                         10.0, 52.0, +200);
-    }
-
-    /**
      * Tests conversion of random points. The test is performed with the Molodensky transform,
      * not the abridged one, because the errors caused by the abridged Molodensky method are
      * too high for this test.
@@ -342,7 +231,7 @@
         create(false);
         tolerance  = Formulas.LINEAR_TOLERANCE * 3;     // To be converted in degrees by ToleranceModifier.GEOGRAPHIC
         zTolerance = Formulas.LINEAR_TOLERANCE * 2;
-        toleranceModifier = ToleranceModifiers.concatenate(ToleranceModifier.GEOGRAPHIC, toleranceModifier);
+//      toleranceModifier = ToleranceModifiers.concatenate(ToleranceModifier.GEOGRAPHIC, toleranceModifier);
         verifyInDomain(new double[] {-179, -85, -500},
                        new double[] {+179, +85, +500},
                        new int[]    {   8,   8,    8},
@@ -377,18 +266,6 @@
     }
 
     /**
-     * Runs the test defined in the GeoAPI-conformance module.
-     *
-     * @throws FactoryException if an error occurred while creating a transform step.
-     * @throws TransformException if a transformation failed.
-     */
-    @Test
-    @DependsOnMethod("testProvider")
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        new ParameterizedTransformTest(new MathTransformFactoryMock(new AbridgedMolodensky())).testAbridgedMolodensky();
-    }
-
-    /**
      * Verifies that creating a Molodensky operation with same source and target ellipsoid and zero translation
      * results in an identity affine transform.
      *
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
index 1f34339..053d7fd 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
@@ -32,11 +32,10 @@
 import org.junit.Test;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.DependsOn;
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.test.CalculationType;
-import org.opengis.test.ToleranceModifier;
+// (all imports removed)
 
 
 /**
@@ -221,21 +220,19 @@
          * Now process to the transform and compares the results with the expected ones.
          */
         tolerance         = 0;          // Results should be strictly identical because we used the same inputs.
-        toleranceModifier = null;
         final double[] transformedData = new double[StrictMath.max(sourceDim, targetDim) * numPts];
         transform.transform(passthroughData, 0, transformedData, 0, numPts);
         assertCoordinatesEqual("PassThroughTransform results do not match the results computed by this test.",
-                targetDim, expectedData, 0, transformedData, 0, numPts, CalculationType.DIRECT_TRANSFORM);
+                targetDim, expectedData, 0, transformedData, 0, numPts, false);
         /*
          * Test inverse transform.
          */
         if (isInverseTransformSupported) {
             tolerance         = 1E-8;
-            toleranceModifier = ToleranceModifier.RELATIVE;
             Arrays.fill(transformedData, Double.NaN);
             transform.inverse().transform(expectedData, 0, transformedData, 0, numPts);
             assertCoordinatesEqual("Inverse of PassThroughTransform do not give back the original data.",
-                    sourceDim, passthroughData, 0, transformedData, 0, numPts, CalculationType.INVERSE_TRANSFORM);
+                    sourceDim, passthroughData, 0, transformedData, 0, numPts, false);
         }
         /*
          * Verify the consistency between different 'transform(…)' methods.
@@ -243,16 +240,6 @@
         final float[] sourceAsFloat = ArraysExt.copyAsFloats(passthroughData);
         final float[] targetAsFloat = verifyConsistency(sourceAsFloat);
         assertEquals("Unexpected length of transformed array.", expectedData.length, targetAsFloat.length);
-        /*
-         * We use a relatively high tolerance threshold because result are
-         * computed using inputs stored as float values.
-         */
-        if (transform instanceof LinearTransform) {
-            tolerance         = 1E-4;
-            toleranceModifier = ToleranceModifier.RELATIVE;
-            assertCoordinatesEqual("PassThroughTransform.transform(…) variants produce inconsistent results.",
-                    sourceDim, expectedData, 0, targetAsFloat, 0, numPts, CalculationType.DIRECT_TRANSFORM);
-        }
     }
 
     /**
@@ -283,7 +270,7 @@
                 0, 0, 1, 0, 0, 0, 0,
                 0, 0, 0, 1, 0, 0, 0,
                 0, 0, 0, 0, 0, 1, 0,
-                0, 0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(0)), null);
+                0, 0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(0)), 0);
         /*
          * The number of pass-through dimensions have decreased from 2 to 1 on both sides of the sub-transform.
          */
@@ -299,6 +286,6 @@
                 1, 0, 0, 0, 0, 0,
                 0, 0, 0, 1, 0, 0,
                 0, 0, 0, 0, 1, 0,
-                0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(2)), null);
+                0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(2)), 0);
     }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java
index d0ee8b7..223b523 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java
@@ -24,7 +24,6 @@
 import static java.lang.StrictMath.*;
 
 // Test dependencies
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestUtilities;
 import org.junit.Test;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java
index 32f2ee0..cc64699 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java
@@ -22,7 +22,6 @@
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.MathTransform2D;
 import org.opengis.referencing.operation.MathTransformFactory;
-import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.matrix.Matrix2;
@@ -36,11 +35,13 @@
 import org.junit.runner.RunWith;
 import org.junit.After;
 import org.junit.Test;
-import org.opengis.test.Assert;
+import org.apache.sis.test.Assert;
 import static org.opengis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.test.referencing.AffineTransformTest;
+import org.junit.Ignore;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.test.referencing.TransformTestCase;
 
 
 /**
@@ -56,7 +57,17 @@
  */
 @RunWith(TestRunner.class)
 @DependsOn({AbstractMathTransformTest.class, ScaleTransformTest.class})
-public strictfp class ProjectiveTransformTest extends AffineTransformTest {
+public strictfp class ProjectiveTransformTest extends TransformTestCase {
+    /**
+     * The factory to use for creating linear transforms.
+     */
+    private final MathTransformFactory mtFactory;
+
+    /**
+     * The matrix for the tested transform.
+     */
+    private Matrix matrix;
+
     /**
      * Tolerance factor for strict comparisons.
      */
@@ -66,14 +77,14 @@
      * For {@link LinearTransformTest} constructor only.
      */
     ProjectiveTransformTest(final MathTransformFactory factory) {
-        super(factory);
+        mtFactory = factory;
     }
 
     /**
      * Creates a new test suite.
      */
     public ProjectiveTransformTest() {
-        super(new MathTransformFactoryBase() {
+        this(new MathTransformFactoryBase() {
             @Override
             public MathTransform createAffineTransform(final Matrix matrix) {
                 final ProjectiveTransform pt;
@@ -108,7 +119,10 @@
     }
 
     /*
-     * Inherit all the tests from GeoAPI:
+     * GeoAPI 3.1 defines the following tests. However since those tests are not available
+     * in GeoAPI 3.0, we put empty placeholder. For running the real test, see for example
+     * the JDK6 branch of Apache SIS.
+     *
      *    - testIdentity1D()
      *    - testIdentity2D()
      *    - testIdentity3D()
@@ -122,6 +136,64 @@
      *    - testDimensionReduction()
      */
 
+    static final String MESSAGE = "This test is not available in GeoAPI 3.0. "
+            + "See Apache SIS JDK6, JDK7 or JDK8 branch for the actual tests.";
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testIdentity1D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testIdentity2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testIdentity3D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testAxisSwapping2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testSouthOrientated2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testTranslatation2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testUniformScale2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testGenericScale2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testRotation2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testGeneral() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testDimensionReduction() throws FactoryException, TransformException {
+    }
+
     /**
      * Tests {@link ProjectiveTransform#optimize()}. In particular this method verifies that a non-square matrix
      * that looks like diagonal is not confused with a real diagonal matrix.
@@ -142,14 +214,13 @@
         });
         transform = mtFactory.createAffineTransform(matrix);
         assertInstanceOf("Non-diagonal matrix shall not be handled by ScaleTransform.", ProjectiveTransform.class, transform);
-        verifyConsistency(1, 2, 3,   -3, -2, -1);
+        verifyConsistency(new float[] {1, 2, 3,   -3, -2, -1});
         /*
          * Remove the "problematic" row. The new transform should now be optimizable.
          */
         matrix = ((MatrixSIS) matrix).removeRows(3, 4);
         transform = mtFactory.createAffineTransform(matrix);
         assertInstanceOf("Diagonal matrix should be handled by a specialized class.", ScaleTransform.class, getOptimizedTransform());
-        verifyConsistency(1, 2, 3,   -3, -2, -1);
     }
 
     /**
@@ -167,7 +238,6 @@
         transform = mtFactory.createAffineTransform(matrix);
         Assert.assertMatrixEquals("Transform shall use the given matrix unmodified.",
                 matrix, ((LinearTransform) transform).getMatrix(), STRICT);
-        verifyConsistency(1, 2, 3,   -3, -2, -1);
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java
index fe9e654..3212123 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java
@@ -25,7 +25,7 @@
 
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.Assert;
+import org.apache.sis.test.Assert;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java
index c49dd50..f2e1a14 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java
@@ -133,7 +133,6 @@
     public void testForwardConsistency() throws TransformException {
         transform = create(false);
         tolerance = 1E-14;
-        isDerivativeSupported = false;          // Actually supported, but our test transform has discontinuities.
         verifyInDomain(CoordinateDomain.RANGE_10, -672445632505596619L);
     }
 
@@ -150,7 +149,6 @@
     public void testInverseConsistency() throws TransformException {
         transform = create(false).inverse();
         tolerance = 1E-12;
-        isDerivativeSupported = false;          // Actually supported, but our test transform has discontinuities.
         verifyInDomain(CoordinateDomain.RANGE_100, 4308397764777385180L);
     }
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java
index 9428cad..b014e95 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java
@@ -24,7 +24,6 @@
 import static java.lang.StrictMath.*;
 
 // Test dependencies
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestUtilities;
 import org.junit.Test;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java
index 2c960f4..127ff60 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java
@@ -28,7 +28,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java
index 817b3f4..17a1e32 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java
@@ -24,7 +24,7 @@
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
index fbc6664..7819155 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
@@ -38,7 +38,7 @@
 import org.junit.Test;
 
 import static java.lang.Double.NaN;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformTestCase.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformTestCase.java
new file mode 100644
index 0000000..1a9ba7e
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformTestCase.java
@@ -0,0 +1,68 @@
+/*
+ * 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.referencing.operation.transform;
+
+import java.util.Random;
+
+import static org.junit.Assume.*;
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+ * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.5
+ * @version 0.6
+ * @module
+ */
+public class TransformTestCase extends org.opengis.test.referencing.TransformTestCase {
+    /**
+     * The deltas to use for approximating math transform derivatives by the finite differences method.
+     */
+    protected double[] derivativeDeltas;
+
+    /**
+     * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+     * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+     *
+     * @param coordinate Ignored.
+     */
+    protected final void verifyDerivative(final double... coordinate) {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    /**
+     * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+     * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+     *
+     * @param minOrdinates    Ignored.
+     * @param maxOrdinates    Ignored.
+     * @param numOrdinates    Ignored.
+     * @param randomGenerator Ignored.
+     */
+    protected final void verifyInDomain(final double[] minOrdinates, final double[] maxOrdinates,
+            final int[] numOrdinates, final Random randomGenerator)
+    {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
index ae4c4aa..00381b5 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
@@ -25,10 +25,9 @@
 
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.Assert;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -52,7 +51,7 @@
         final TranslationTransform tr = new TranslationTransform(matrix.getNumRow(), elements);
         assertEquals("sourceDimensions", dimensions, tr.getSourceDimensions());
         assertEquals("targetDimensions", dimensions, tr.getTargetDimensions());
-        Assert.assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
+        assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
         assertArrayEquals("elements", elements, tr.getExtendedElements(), 0.0);
         transform = tr;
         validate();
@@ -106,7 +105,7 @@
         final TranslationTransform tr = new TranslationTransform(4, elements);
         assertEquals("sourceDimensions", 3, tr.getSourceDimensions());
         assertEquals("targetDimensions", 3, tr.getTargetDimensions());
-        Assert.assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
+        assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
         assertArrayEquals("elements", elements, tr.getExtendedElements(), 0.0);
         transform = tr;
         validate();
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java
index c702c0c..b32273b 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java
@@ -30,7 +30,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java
index ac5e315..694e012 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java
@@ -53,7 +53,7 @@
 import org.apache.sis.util.Version;
 
 // Branch-dependent imports
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -245,7 +245,7 @@
          * ────────────────    EPSG IDENTIFIERS    ────────────────────────────────────
          */
         final StringBuilder buffer = new StringBuilder();
-        for (final Identifier id : method.getIdentifiers()) {
+        for (final ReferenceIdentifier id : method.getIdentifiers()) {
             if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
                 if (buffer.length() != 0) {
                     buffer.append(", ");
@@ -387,7 +387,7 @@
     private void writeName(final ParameterDescriptor<?> param) throws IOException {
         final int td = openTag("td class=\"sep\"");
         openTag("details");
-        final Identifier name = param.getName();
+        final ReferenceIdentifier name = param.getName();
         final String codeSpace = name.getCodeSpace();
         if (Constants.EPSG.equalsIgnoreCase(codeSpace)) {
             println("summary", escape(name.getCode()));
@@ -539,8 +539,8 @@
     /**
      * Returns the first EPSG code found in the given collection, or {@code null} if none.
      */
-    private static String getFirstEpsgCode(final Iterable<? extends Identifier> identifiers) {
-        for (final Identifier id : identifiers) {
+    private static String getFirstEpsgCode(final Iterable<? extends ReferenceIdentifier> identifiers) {
+        for (final ReferenceIdentifier id : identifiers) {
             if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
                 return id.getCode();
             }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateReferenceSystems.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateReferenceSystems.java
deleted file mode 100644
index d24443b..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateReferenceSystems.java
+++ /dev/null
@@ -1,825 +0,0 @@
-/*
- * 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.referencing.report;
-
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Set;
-import java.util.Map;
-import java.util.HashSet;
-import java.util.TreeMap;
-import java.util.NavigableMap;
-import java.io.File;
-import java.io.IOException;
-
-import org.opengis.metadata.Identifier;
-import org.opengis.util.FactoryException;
-import org.opengis.util.InternationalString;
-import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.cs.CartesianCS;
-import org.opengis.referencing.cs.SphericalCS;
-import org.opengis.referencing.cs.CoordinateSystem;
-import org.opengis.referencing.crs.CompoundCRS;
-import org.opengis.referencing.crs.VerticalCRS;
-import org.opengis.referencing.crs.GeocentricCRS;
-import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.referencing.crs.EngineeringCRS;
-import org.opengis.referencing.crs.GeneralDerivedCRS;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.opengis.referencing.datum.Datum;
-import org.opengis.referencing.datum.VerticalDatumType;
-import org.opengis.referencing.operation.OperationMethod;
-import org.opengis.test.report.AuthorityCodesReport;
-import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.internal.referencing.DeprecatedCode;
-import org.apache.sis.internal.util.Constants;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.referencing.crs.AbstractCRS;
-import org.apache.sis.referencing.cs.AxesConvention;
-import org.apache.sis.util.iso.DefaultNameSpace;
-import org.apache.sis.util.logging.Logging;
-import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.util.Deprecable;
-import org.apache.sis.util.Utilities;
-import org.apache.sis.util.Version;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Generates a list of supported Coordinate Reference Systems in the current directory.
- * This class is for manual execution after the EPSG database has been updated,
- * or the projection implementations changed.
- *
- * <p><b>WARNING:</b>
- * this class implements heuristic rules for nicer sorting (e.g. of CRS having numbers as Roman letters).
- * Those heuristic rules were determined specifically for the EPSG dataset expanded with WMS codes.
- * This class is not likely to produce good results for any other authorities, and many need to be updated
- * after any upgrade of the EPSG dataset.</p>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   0.7
- * @module
- */
-public final strictfp class CoordinateReferenceSystems extends AuthorityCodesReport {
-    /**
-     * The titles of some sections where to group CRS. By default CRS are grouped by datum names.
-     * But if a name is listed in this map, then that alternative name will be used for grouping purpose.
-     * Sometime the change is only cosmetic (e.g. "Reseau Geodesique Francais" → "Réseau Géodésique Français").
-     * But sometime the changes have the effect of merging different datum under the same section.
-     * For example we merge the "Arc 1950" and "Arc 1960" sections into a single "Arc" section,
-     * since those sections were small and we do not want to scatter the HTML page with too many sections.
-     * However we do not merge "NAD83" and "NAD83(HARN)" because those sections are already quite large,
-     * and merging them will result in a too large section.
-     *
-     * <p>The decision to merge or not is arbitrary. Generally, we try to avoid small sections (less that 5 CRS)
-     * but without merging together unrelated datum. Every CRS having a datum whose name <em>starts</em> with a
-     * value in the left column will be reported in the section given in the right column.</p>
-     */
-    private static final NavigableMap<String,String> SECTION_TITLES = new TreeMap<>();
-    static {
-        rd("American Samoa",                                          "American Samoa");
-        rd("Arc",                                                     "Arc");
-        rd("Ancienne Triangulation Francaise",                        "Ancienne Triangulation Française");
-        rd("Australian Geodetic Datum",                               "Australian Geodetic Datum");
-        rd("Australian Height Datum",                                 "Australian Height Datum");
-        rd("Azores Central Islands",                                  "Azores Islands");
-        rd("Azores Occidental Islands",                               "Azores Islands");
-        rd("Azores Oriental Islands",                                 "Azores Islands");
-        rd("Baltic",                                                  "Baltic");
-        rd("Batavia",                                                 "Batavia");
-        rd("Bermuda",                                                 "Bermuda");
-        rd("Bogota 1975",                                             "Bogota 1975");
-        rd("Carthage",                                                "Carthage");
-        rd("Bern 1938",                                               "Bern / CH1903");
-        rd("Cais",                                                    "Cais");
-        rd("Cayman Brac",                                             "Cayman Islands");
-        rd("Cayman Islands",                                          "Cayman Islands");
-        rd("CH1903",                                                  "Bern / CH1903");
-        rd("CH1903+",                                                 "Bern / CH1903");
-        rd("Canadian Geodetic Vertical Datum",                        "Canadian Geodetic Vertical Datum");
-        rd("Chatham Islands Datum",                                   "Chatham Islands Datum");
-        rd("Corrego Alegre",                                          "Corrego Alegre");
-        rd("Croatian Terrestrial Reference System",                   "Croatian Reference System");
-        rd("Croatian Vertical Reference Datum",                       "Croatian Reference System");
-        rd("Danger 1950",                                             "Saint Pierre et Miquelon 1950");
-        rd("Dansk",                                                   "Dansk");
-        rd("Dealul Piscului",                                         "Dealul Piscului");
-        rd("Deutsches Haupthoehennetz",                               "Deutsches Haupthoehennetz");
-        rd("Douala",                                                  "Douala");
-        rd("Dunedin",                                                 "Dunedin");
-        rd("Dunedin-Bluff",                                           "Dunedin");
-        rd("EGM2008 geoid",                                           "EGM geoid");
-        rd("EGM84 geoid",                                             "EGM geoid");
-        rd("EGM96 geoid",                                             "EGM geoid");
-        rd("Egypt",                                                   "Egypt");
-        rd("EPSG example",                                            "EPSG example");
-        rd("Estonia",                                                 "Estonia");
-        rd("European Datum",                                          "European Datum");
-        rd("European Terrestrial Reference Frame",                    "European Terrestrial Reference Frame");
-        rd("European Vertical Reference Frame",                       "European Vertical Reference Frame");
-        rd("Fahud",                                                   "Fahud");
-        rd("Fao",                                                     "Fao");
-        rd("Fehmarnbelt",                                             "Fehmarnbelt");
-        rd("Faroe Datum",                                             "Faroe Islands");
-        rd("Faroe Islands",                                           "Faroe Islands");
-        rd("fk89",                                                    "Faroe Islands");
-        rd("Fiji",                                                    "Fiji");
-        rd("Gan 1970",                                                "Gandajika");
-        rd("Grand Cayman",                                            "Grand Cayman");
-        rd("Greek",                                                   "Greek");
-        rd("Greenland",                                               "Greenland");
-        rd("Guadeloupe",                                              "Guadeloupe");
-        rd("Guam",                                                    "Guam");
-        rd("Gunung Segara",                                           "Gunung Segara");
-        rd("Helsinki",                                                "Helsinki");
-        rd("High Water",                                              "High Water");
-        rd("Higher High Water",                                       "High Water");
-        rd("Highest Astronomical Tide",                               "High Water");
-        rd("Hong Kong",                                               "Hong Kong");
-        rd("Hungarian",                                               "Hungarian Datum");
-        rd("IG05",                                                    "Israeli Grid");
-        rd("IGb",                                                     "IGb");
-        rd("IGN",                                                     "IGN");
-        rd("IGS",                                                     "IGS");
-        rd("Indian",                                                  "Indian");
-        rd("International Great Lakes Datum",                         "International Great Lakes Datum");
-        rd("International Terrestrial Reference Frame",               "International Terrestrial Reference Frame");
-        rd("Islands Net",                                             "Islands Net");
-        rd("Israeli Geodetic Datum",                                  "Israeli Geodetic Datum");
-        rd("Jamaica",                                                 "Jamaica");
-        rd("Japanese Geodetic Datum 2000",                            "Japanese Geodetic Datum 2000");
-        rd("Japanese Geodetic Datum 2011",                            "Japanese Geodetic Datum 2011");
-        rd("Japanese Standard Levelling Datum",                       "Japanese Standard Levelling Datum");
-        rd("Kalianpur",                                               "Kalianpur");
-        rd("Kertau",                                                  "Kertau");
-        rd("KOC Construction Datum",                                  "KOC Construction Datum / Well Datum");
-        rd("KOC Well Datum",                                          "KOC Construction Datum / Well Datum");
-        rd("Korean Datum",                                            "Korean Datum");
-        rd("Kuwait Oil Company",                                      "Kuwait Oil Company / Kuwait Utility");
-        rd("Kuwait PWD",                                              "Kuwait Oil Company / Kuwait Utility");
-        rd("Kuwait Utility",                                          "Kuwait Oil Company / Kuwait Utility");
-        rd("Lao",                                                     "Lao");
-        rd("Latvia",                                                  "Latvia");
-        rd("Lisbon",                                                  "Lisbon");
-        rd("Lower Low Water Large Tide",                              "Low Water");
-        rd("Lowest Astronomical Tide",                                "Low Water");
-        rd("Macao",                                                   "Macao");
-        rd("Makassar",                                                "Makassar");
-        rd("Manoca",                                                  "Manoca");
-        rd("Martinique",                                              "Martinique");
-        rd("Maupiti",                                                 "Maupiti");
-        rd("Mean High Water",                                         "Mean Sea Level");
-        rd("Mean Higher High Water",                                  "Mean Sea Level");
-        rd("Mean Low Water",                                          "Mean Sea Level");
-        rd("Mean Lower Low Water",                                    "Mean Sea Level");
-        rd("Missao Hidrografico Angola y Sao Tome 1951",              "Missao Hidrografico Angola y Sao Tome");
-        rd("Mhast (offshore)",                                        "Missao Hidrografico Angola y Sao Tome");
-        rd("Mhast (onshore)",                                         "Missao Hidrografico Angola y Sao Tome");
-        rd("Militar-Geographische Institut (Ferro)",                  "Militar-Geographische Institut");
-        rd("MOMRA",                                                   "MOMRA");
-        rd("Monte Mario (Rome)",                                      "Monte Mario");
-        rd("Moorea",                                                  "Moorea");
-        rd("Nahrwan",                                                 "Nahrwan");
-        rd("Naparima",                                                "Naparima");
-        rd("Nivellement General de la Corse",                         "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General de la France",                        "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General de Nouvelle Caledonie",               "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General de Polynesie Francaise",              "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General du Luxembourg",                       "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General Guyanais",                            "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("NGO 1948",                                                "NGO 1948");
-        rd("Nouvelle Triangulation Francaise",                        "Nouvelle Triangulation Française");
-        rd("NAD83 Canadian Spatial Reference System",                 "North American Datum 1983 — Canadian Spatial Reference System");
-        rd("NAD83 (Continuously Operating Reference Station 1996)",   "North American Datum 1983 — Continuously Operating Reference Station 1996");       // For better sort order.
-        rd("NAD83 (Federal Base Network)",                            "North American Datum 1983 — Federal Base Network");
-        rd("NAD83 (High Accuracy Reference Network)",                 "North American Datum 1983 — High Accuracy Reference Network");
-        rd("NAD83 (High Accuracy Reference Network - Corrected)",     "North American Datum 1983 — High Accuracy Reference Network");
-        rd("NAD83 (National Spatial Reference System 2007)",          "North American Datum 1983 — National Spatial Reference System 2007");
-        rd("NAD83 (National Spatial Reference System 2011)",          "North American Datum 1983 — National Spatial Reference System 2011");
-        rd("NAD83 (National Spatial Reference System MA11)",          "North American Datum 1983 — National Spatial Reference System MA11 / PA11");
-        rd("NAD83 (National Spatial Reference System PA11)",          "North American Datum 1983 — National Spatial Reference System MA11 / PA11");
-        rd("North American Datum of 1983 (CSRS)",                     "North American Datum 1983 — CSRS");
-        rd("North American Datum of 1983 (CSRS96)",                   "North American Datum 1983 — CSRS");
-        rd("New Zealand Vertical Datum",                              "New Zealand Vertical Datum");
-        rd("Norway Normal Null",                                      "Norway Normal Null");
-        rd("Ordnance Datum Newlyn",                                   "Ordnance Datum Newlyn");
-        rd("OSGB",                                                    "OSGB");
-        rd("Parametry Zemli 1990",                                    "Parametry Zemli 1990");
-        rd("PDO Height Datum 1993",                                   "PDO Survey / Height Datum 1993");
-        rd("PDO Survey Datum 1993",                                   "PDO Survey / Height Datum 1993");
-        rd("Pitcairn",                                                "Pitcairn");
-        rd("Port Moresby",                                            "Port Moresby");
-        rd("Porto Santo",                                             "Porto Santo");
-        rd("Posiciones Geodésicas Argentinas",                        "Posiciones Geodésicas Argentinas");
-        rd("Puerto Rico",                                             "Puerto Rico");
-        rd("Qatar",                                                   "Qatar");
-        rd("Qornoq",                                                  "Qornoq");
-        rd("Reseau Geodesique de Nouvelle Caledonie",                 "Réseau Géodésique de Nouvelle-Calédonie");
-        rd("Reseau National Belge",                                   "Réseau National Belge");
-        rd("Reunion",                                                 "Réunion");
-        rd("Rikets hojdsystem",                                       "Rikets hojdsystem");
-        rd("Santa Cruz",                                              "Santa Cruz");
-        rd("Serbian",                                                 "Serbian Reference System / Network");
-        rd("Sierra Leone",                                            "Sierra Leone");
-        rd("SIRGAS",                                                  "SIRGAS");
-        rd("Slovenia",                                                "Slovenia");
-        rd("Slovenian",                                               "Slovenia");
-        rd("South American Datum",                                    "South American Datum");
-        rd("Sri Lanka",                                               "Sri Lanka");
-        rd("Stockholm 1938",                                          "Stockholm 1938");
-        rd("St. Helena",                                              "St. Helena");
-        rd("System of the Unified Trigonometrical Cadastral Network", "System of the Unified Trigonometrical Cadastral Network");
-        rd("Tahaa",                                                   "Tahaa");
-        rd("Tahiti",                                                  "Tahiti");
-        rd("Taiwan",                                                  "Taiwan");
-        rd("Tananarive 1925",                                         "Tananarive 1925");
-        rd("Tokyo",                                                   "Tokyo");
-        rd("Viti Levu",                                               "Viti Levu");
-        rd("Voirol",                                                  "Voirol");
-        rd("WGS 72 Transit Broadcast Ephemeris",                      "World Geodetic System 1972 — Transit Broadcast Ephemeris");
-        rd("World Geodetic System 1984",                              "World Geodetic System 1984");
-        rd("Yellow Sea",                                              "Yellow Sea");
-    }
-
-    /**
-     * The datums from the above list which are deprecated, but that we do not want to replace by the non-deprecated
-     * datum. We disable some replacements when they allow better sorting of deprecated CRS.
-     */
-    private static final Set<String> KEEP_DEPRECATED_DATUM = new HashSet<>(Arrays.asList(
-        "Dealul Piscului 1970"));           // Datum does not exist but is an alias for S-42 in Romania.
-
-    /**
-     * Shortcut for {@link #SECTION_TITLES} initialization.
-     * {@code "rd"} stands for "rename datum".
-     */
-    private static void rd(final String datum, final String display) {
-        assertNull(datum, SECTION_TITLES.put(datum, display));
-    }
-
-    /**
-     * Words to ignore in a datum name in order to detect if a CRS name is the acronym of the datum name.
-     */
-    private static final Set<String> DATUM_WORDS_TO_IGNORE = new HashSet<>(Arrays.asList(
-            "of",           // VIVD:   Virgin Islands Vertical Datum of 2009
-            "de",           // RRAF:   Reseau de Reference des Antilles Francaises
-            "des",          // RGAF:   Reseau Geodesique des Antilles Francaises
-            "la",           // RGR:    Reseau Geodesique de la Reunion
-            "et",           // RGSPM:  Reseau Geodesique de Saint Pierre et Miquelon
-            "para",         // SIRGAS: Sistema de Referencia Geocentrico para America del Sur 1995
-            "del",          // SIRGAS: Sistema de Referencia Geocentrico para America del Sur 1995
-            "las",          // SIRGAS: Sistema de Referencia Geocentrico para las AmericaS 2000
-            "Tides"));      // MLWS:   Mean Low Water Spring Tides
-
-    /**
-     * The keywords before which to cut the CRS names when sorting by alphabetical order.
-     * The main intent here is to preserve the "far west", "west", "central west", "central",
-     * "central east", "east", "far east" order.
-     */
-    private static final String[] CUT_BEFORE = {
-        " far west",        // "MAGNA-SIRGAS / Colombia Far West zone"
-        " far east",
-        " west",            // "Bogota 1975 / Colombia West zone"
-        " east",            // "Bogota 1975 / Colombia East Central zone"
-        " central",         // "Korean 1985 / Central Belt" (between "East Belt" and "West Belt")
-        " old central",     // "NAD Michigan / Michigan Old Central"
-        " bogota zone",     // "Bogota 1975 / Colombia Bogota zone"
-        // Do not declare "North" and "South" as it causes confusion with "WGS 84 / North Pole" and other cases.
-    };
-
-    /**
-     * The keywords after which to cut the CRS names when sorting by alphabetical order.
-     *
-     * Note: alphabetical sorting of Roman numbers work for zones from I to VIII inclusive.
-     * If there is more zones (for example with "JGD2000 / Japan Plane Rectangular"), then
-     * we need to cut before those numbers in order to use sorting by EPSG codes instead.
-     *
-     * Note 2: if alphabetical sorting is okay for Roman numbers, it is actually preferable
-     * because it give better position of names with height like "zone II + NGF IGN69 height".
-     */
-    private static final String[] CUT_AFTER = {
-        " cs ",                     // "JGD2000 / Japan Plane Rectangular CS IX"
-        " tm",                      // "ETRS89 / TM35FIN(E,N)" — we want to not interleave them between "TM35" and "TM36".
-        " dktm",                    // "ETRS89 / DKTM1 + DVR90 height"
-        "-gk",                      // "ETRS89 / ETRS-GK19FIN"
-        " philippines zone ",       // "Luzon 1911 / Philippines zone IV"
-        " california zone ",        // "NAD27 / California zone V"
-        " ngo zone ",               // "NGO 1948 (Oslo) / NGO zone I"
-        " lambert zone ",           // "NTF (Paris) / Lambert zone II + NGF IGN69 height"
-        "fiji 1956 / utm zone "     // Two zones: 60S and 1S with 60 before 1.
-    };
-
-    /**
-     * The symbol to write in from of EPSG code of CRS having an axis order different
-     * then the (longitude, latitude) one.
-     */
-    private static final char YX_ORDER = '\u21B7';
-
-    /**
-     * The factory which create CRS instances.
-     */
-    private final CRSAuthorityFactory factory;
-
-    /**
-     * The datum from the {@link #SECTION_TITLES} that we didn't found after we processed all codes.
-     * Used for verification purpose only.
-     */
-    private final Set<String> unusedDatumMapping;
-
-    /**
-     * Creates a new instance.
-     */
-    private CoordinateReferenceSystems() throws FactoryException {
-        super(null);
-        unusedDatumMapping = new HashSet<>(SECTION_TITLES.keySet());
-        properties.setProperty("TITLE",           "Apache SIS™ Coordinate Reference System (CRS) codes");
-        properties.setProperty("PRODUCT.NAME",    "Apache SIS™");
-        properties.setProperty("PRODUCT.VERSION", getVersion());
-        properties.setProperty("PRODUCT.URL",     "https://sis.apache.org");
-        properties.setProperty("JAVADOC.GEOAPI",  "https://www.geoapi.org/snapshot/javadoc");
-        properties.setProperty("FACTORY.NAME",    "EPSG");
-        properties.setProperty("FACTORY.VERSION", "9.9.1");
-        properties.setProperty("FACTORY.VERSION.SUFFIX", ", together with other sources");
-        properties.setProperty("PRODUCT.VERSION.SUFFIX", " (provided that <a href=\"https://sis.apache.org/epsg.html\">a connection to an EPSG database exists</a>)");
-        properties.setProperty("DESCRIPTION", "<p><b>Notation:</b></p>\n" +
-                "<ul>\n" +
-                "  <li>The " + YX_ORDER + " symbol in front of authority codes (${PERCENT.ANNOTATED} of them) identifies" +
-                " left-handed coordinate systems (for example with <var>latitude</var> axis before <var>longitude</var>).</li>\n" +
-                "  <li>The <del>codes with a strike</del> (${PERCENT.DEPRECATED} of them) identify deprecated CRS." +
-                " In some cases, the remarks column indicates the replacement.</li>\n" +
-                "</ul>");
-        factory = CRS.getAuthorityFactory(null);
-        add(factory);
-    }
-
-    /**
-     * Generates the HTML report.
-     *
-     * @param  args  ignored.
-     * @throws FactoryException if an error occurred while fetching the CRS.
-     * @throws IOException if an error occurred while writing the HTML file.
-     */
-    @SuppressWarnings("UseOfSystemOutOrSystemErr")
-    public static void main(final String[] args) throws FactoryException, IOException {
-        Locale.setDefault(Locale.US);   // We have to use this hack for now because exceptions are formatted in the current locale.
-        final CoordinateReferenceSystems writer = new CoordinateReferenceSystems();
-        final File file = writer.write(new File("CoordinateReferenceSystems.html"));
-        System.out.println("Created " + file.getAbsolutePath());
-        if (!writer.unusedDatumMapping.isEmpty()) {
-            System.out.println();
-            System.out.println("WARNING: the following datums were expected but not found. Maybe their spelling changed?");
-            for (final String name : writer.unusedDatumMapping) {
-                System.out.print("  - ");
-                System.out.println(name);
-            }
-        }
-    }
-
-    /**
-     * Returns the current Apache SIS version, with the {@code -SNAPSHOT} trailing part omitted.
-     *
-     * @return the current Apache SIS version.
-     */
-    private static String getVersion() {
-        String version = Version.SIS.toString();
-        final int snapshot = version.lastIndexOf('-');
-        if (snapshot >= 2) {
-            version = version.substring(0, snapshot);
-        }
-        return version;
-    }
-
-    /**
-     * Creates the text to show in the "Remarks" column for the given CRS.
-     */
-    private String getRemark(final CoordinateReferenceSystem crs) {
-        if (crs instanceof GeographicCRS) {
-            return (crs.getCoordinateSystem().getDimension() == 3) ? "Geographic 3D" : "Geographic";
-        }
-        if (crs instanceof GeneralDerivedCRS) {
-            final OperationMethod method = ((GeneralDerivedCRS) crs).getConversionFromBase().getMethod();
-            final Identifier identifier = IdentifiedObjects.getIdentifier(method, Citations.EPSG);
-            if (identifier != null) {
-                return "<a href=\"CoordinateOperationMethods.html#" + identifier.getCode()
-                       + "\">" + method.getName().getCode().replace('_', ' ') + "</a>";
-            }
-        }
-        if (crs instanceof GeocentricCRS) {
-            final CoordinateSystem cs = crs.getCoordinateSystem();
-            if (cs instanceof CartesianCS) {
-                return "Geocentric (Cartesian coordinate system)";
-            } else if (cs instanceof SphericalCS) {
-                return "Geocentric (spherical coordinate system)";
-            }
-            return "Geocentric";
-        }
-        if (crs instanceof VerticalCRS) {
-            final VerticalDatumType type = ((VerticalCRS) crs).getDatum().getVerticalDatumType();
-            return CharSequences.camelCaseToSentence(type.name().toLowerCase(getLocale())) + " height";
-        }
-        if (crs instanceof CompoundCRS) {
-            final StringBuilder buffer = new StringBuilder();
-            for (final CoordinateReferenceSystem component : ((CompoundCRS) crs).getComponents()) {
-                if (buffer.length() != 0) {
-                    buffer.append(" + ");
-                }
-                buffer.append(getRemark(component));
-            }
-            return buffer.toString();
-        }
-        if (crs instanceof EngineeringCRS) {
-            return "Engineering (" + crs.getCoordinateSystem().getName().getCode() + ')';
-        }
-        return "";
-    }
-
-    /**
-     * Omits the trailing number, if any.
-     * For example if the given name is "Abidjan 1987", then this method returns "Abidjan".
-     */
-    private static String omitTrailingNumber(String name) {
-        int i = CharSequences.skipTrailingWhitespaces(name, 0, name.length());
-        while (i != 0) {
-            final char c = name.charAt(--i);
-            if (c < '0' || c > '9') {
-                name = name.substring(0, CharSequences.skipTrailingWhitespaces(name, 0, i+1));
-                break;
-            }
-        }
-        return name;
-    }
-
-    /**
-     * If the first word of the CRS name seems to be an acronym of the datum name,
-     * puts that acronym in a {@code <abbr title="datum name">...</abbr>} element.
-     */
-    static String insertAbbreviationTitle(final String crsName, final String datumName) {
-        int s = crsName.indexOf(' ');
-        if (s < 0) s = crsName.length();
-        int p = crsName.indexOf('(');
-        if (p >= 0 && p < s) s = p;
-        p = datumName.indexOf('(');
-        if (p < 0) p = datumName.length();
-        final String acronym = crsName.substring(0, s);
-        final String ar = omitTrailingNumber(acronym);
-        final String dr = omitTrailingNumber(datumName.substring(0, p));
-        if (dr.startsWith(ar)) {
-            return crsName;                                 // Avoid redudancy between CRS name and datum name.
-        }
-        /*
-         * If the first CRS word does not seem to be an acronym of the datum name, verify
-         * if there is some words that we should ignore in the datum name and try again.
-         */
-        if (!CharSequences.isAcronymForWords(ar, dr)) {
-            final String[] words = (String[]) CharSequences.split(dr, ' ');
-            int n = 0;
-            for (final String word : words) {
-                if (!DATUM_WORDS_TO_IGNORE.contains(word)) {
-                    words[n++] = word;
-                }
-            }
-            if (n == words.length || n < 2) {
-                return crsName;
-            }
-            final StringBuilder b = new StringBuilder();
-            for (int i=0; i<n; i++) {
-                if (i != 0) b.append(' ');
-                b.append(words[i]);
-            }
-            if (!CharSequences.isAcronymForWords(ar, b)) {
-                return crsName;
-            }
-        }
-        return "<abbr title=\"" + datumName + "\">" + acronym + "</abbr>" + crsName.substring(s);
-    }
-
-    /**
-     * Invoked when a CRS has been successfully created. This method modifies the default
-     * {@link org.opengis.test.report.AuthorityCodesReport.Row} attribute values created
-     * by GeoAPI.
-     *
-     * @param  code    the authority code of the created object.
-     * @param  object  the object created from the given authority code.
-     * @return the created row, or {@code null} if the row should be ignored.
-     */
-    @Override
-    protected Row createRow(final String code, final IdentifiedObject object) {
-        final Row row = super.createRow(code, object);
-        final CoordinateReferenceSystem crs = (CoordinateReferenceSystem) object;
-        final CoordinateReferenceSystem crsXY = AbstractCRS.castOrCopy(crs).forConvention(AxesConvention.RIGHT_HANDED);
-        if (!Utilities.deepEquals(crs.getCoordinateSystem(), crsXY.getCoordinateSystem(), ComparisonMode.IGNORE_METADATA)) {
-            row.annotation = YX_ORDER;
-        }
-        CoordinateReferenceSystem replacement = crs;
-        row.remark = getRemark(crs);
-        /*
-         * If the object is deprecated, find the replacement.
-         * We do not take the whole comment because it may be pretty long.
-         */
-        if (object instanceof Deprecable) {
-            row.isDeprecated = ((Deprecable) object).isDeprecated();
-            if (row.isDeprecated) {
-                String replacedBy = null;
-                InternationalString i18n = object.getRemarks();
-                for (final Identifier id : object.getIdentifiers()) {
-                    if (id instanceof Deprecable && ((Deprecable) id).isDeprecated()) {
-                        i18n = ((Deprecable) id).getRemarks();
-                        if (id instanceof DeprecatedCode) {
-                            replacedBy = ((DeprecatedCode) id).replacedBy;
-                        }
-                        break;
-                    }
-                }
-                if (i18n != null) {
-                    row.remark = i18n.toString(getLocale());
-                }
-                /*
-                 * If a replacement exists for a deprecated CRS, use the datum of the replacement instead of
-                 * the datum of the deprecated CRS for determining in which section to put the CRS. The reason
-                 * is that some CRS are deprecated because they were associated to the wrong datum, in which
-                 * case the deprecated CRS would appear in the wrong section if we do not apply this correction.
-                 */
-                if (!KEEP_DEPRECATED_DATUM.contains(CRS.getSingleComponents(crs).get(0).getDatum().getName().getCode())) {
-                    if (replacedBy != null) try {
-                        replacement = factory.createCoordinateReferenceSystem("EPSG:" + replacedBy);
-                    } catch (FactoryException e) {
-                        // Ignore - keep the datum of the deprecated object.
-                    }
-                }
-            }
-        }
-        ((ByName) row).setup(CRS.getSingleComponents(replacement).get(0).getDatum(), unusedDatumMapping);
-        return row;
-    }
-
-    /**
-     * Invoked when a CRS creation failed. This method modifies the default
-     * {@link org.opengis.test.report.AuthorityCodesReport.Row} attribute values
-     * created by GeoAPI.
-     *
-     * @param  code       the authority code of the object to create.
-     * @param  exception  the exception that occurred while creating the identified object.
-     * @return the created row, or {@code null} if the row should be ignored.
-     */
-    @Override
-    protected Row createRow(final String code, final FactoryException exception) {
-        if (code.startsWith(Constants.PROJ4 + DefaultNameSpace.DEFAULT_SEPARATOR)) {
-            return null;
-        }
-        final Row row = super.createRow(code, exception);
-        try {
-            row.name = factory.getDescriptionText(code).toString(getLocale());
-        } catch (FactoryException e) {
-            Logging.unexpectedException(null, CoordinateReferenceSystems.class, "createRow", e);
-        }
-        if (code.startsWith("AUTO2:")) {
-            // It is normal to be unable to instantiate an "AUTO" CRS,
-            // because those authority codes need parameters.
-            row.hasError = false;
-            row.remark = "Projected";
-            ((ByName) row).setup(CommonCRS.WGS84.datum(), unusedDatumMapping);
-        } else {
-            row.remark = exception.getLocalizedMessage();
-            ((ByName) row).setup(null, unusedDatumMapping);
-        }
-        return row;
-    }
-
-    /**
-     * Invoked by {@link AuthorityCodesReport} for creating a new row instance.
-     *
-     * @return the new row instance.
-     */
-    @Override
-    protected Row newRow() {
-        return new ByName();
-    }
-
-
-
-
-    /**
-     * A row with an natural ordering that use the first part of the name before to use the authority code.
-     * We use only the part of the name prior some keywords (e.g. {@code "zone"}).
-     * For example if the following codes:
-     *
-     * {@preformat text
-     *    EPSG:32609    WGS 84 / UTM zone 9N
-     *    EPSG:32610    WGS 84 / UTM zone 10N
-     * }
-     *
-     * We compare only the "WGS 84 / UTM" string, then the code. This is a reasonably easy way to keep a more
-     * natural ordering ("9" sorted before "10", "UTM North" projections kept together and same for South).
-     */
-    private static final class ByName extends Row {
-        /**
-         * A string derived from the {@link #name} to use for sorting.
-         */
-        private String reducedName;
-
-        /**
-         * The datum name, or {@code null} if unknown.
-         * If non-null, this is used for grouping CRS names by sections.
-         */
-        String section;
-
-        /**
-         * Creates a new row.
-         */
-        ByName() {
-        }
-
-        /**
-         * Computes the {@link #reducedName} field value.
-         */
-        final void setup(final Datum datum, final Set<String> unusedDatumMapping) {
-            final String datumName;
-            if (datum != null) {
-                datumName = datum.getName().getCode();
-            } else {
-                // Temporary patch (TODO: remove after we implemented the missing methods in SIS)
-                if (name.startsWith("NSIDC EASE-Grid")) {
-                    datumName = "Unspecified datum";
-                } else if (code.equals("EPSG:2163")) {
-                    datumName = "Unspecified datum";
-                } else if (code.equals("EPSG:5818")) {
-                    datumName = "Seismic bin grid datum";
-                } else {
-                    datumName = null;       // Keep ordering based on the name.
-                }
-            }
-            if (datumName != null) {
-                final String prefix;
-                final Map.Entry<String,String> group = SECTION_TITLES.floorEntry(datumName);
-                if (group != null && datumName.startsWith(prefix = group.getKey())) {
-                    unusedDatumMapping.remove(prefix);
-                    section = group.getValue();
-                } else {
-                    section = datumName;
-                }
-            }
-            /*
-             * Get a copy of the name in all lower case.
-             */
-            final StringBuilder b = new StringBuilder(name);
-            for (int i=0; i<b.length(); i++) {
-                b.setCharAt(i, Character.toLowerCase(b.charAt(i)));
-            }
-            /*
-             * Cut the string to a shorter length if we find a keyword.
-             * This will result in many string equals, which will then be sorted by EPSG codes.
-             * This is useful when the EPSG codes give a better ordering than the alphabetic one
-             * (for example with Roman numbers).
-             */
-            int s = 0;
-            for (final String keyword : CUT_BEFORE) {
-                int i = b.lastIndexOf(keyword);
-                if (i > 0 && (s == 0 || i < s)) s = i;
-            }
-            for (final String keyword : CUT_AFTER) {
-                int i = b.lastIndexOf(keyword);
-                if (i >= 0) {
-                    i += keyword.length();
-                    if (i > s) s = i;
-                }
-            }
-            if (s != 0) b.setLength(s);
-            uniformizeZoneNumber(b);
-            reducedName = b.toString();
-            if (datumName != null) {
-                name = insertAbbreviationTitle(name, datumName);
-            }
-        }
-
-        /**
-         * If the string ends with a number optionally followed by "N" or "S", replaces the hemisphere
-         * symbol by a sign and makes sure that the number uses at least 3 digits (e.g. "2N" → "+002").
-         * This string will be used for better sorting order.
-         */
-        private static void uniformizeZoneNumber(final StringBuilder b) {
-            if (b.indexOf("/") < 0) {
-                /*
-                 * Do not process names like "WGS 84". We want to process only names like "WGS 84 / UTM zone 2N",
-                 * otherwise the replacement of "WGS 84" by "WGS 084" causes unexpected sorting.
-                 */
-                return;
-            }
-            int  i = b.length();
-            char c = b.charAt(i - 1);
-            if (c == ')') {
-                // Ignore suffix like " (ftUS)".
-                i = b.lastIndexOf(" (");
-                if (i < 0) return;
-                c = b.charAt(i - 1);
-            }
-            char sign;
-            switch (c) {
-                default:            sign =  0;       break;
-                case 'e': case 'n': sign = '+'; i--; break;
-                case 'w': case 's': sign = '-'; i--; break;
-            }
-            int upper = i;
-            do {
-                if (i == 0) return;
-                c = b.charAt(--i);
-            } while (c >= '0' && c <= '9');
-            switch (upper - ++i) {
-                case 2: b.insert(i,  '0'); upper++;  break;     // Found 2 digits.
-                case 1: b.insert(i, "00"); upper+=2; break;     // Only one digit found.
-                case 0: return;                                 // No digit.
-            }
-            if (sign != 0) {
-                b.insert(i, sign);
-                upper++;
-            }
-            b.setLength(upper);
-        }
-
-        /**
-         * Compares this row with the given row for ordering by name.
-         */
-        @Override
-        public int compareTo(final Row o) {
-            int n = reducedName.compareTo(((ByName) o).reducedName);
-            if (n == 0) {
-                n = super.compareTo(o);
-            }
-            return n;
-        }
-    }
-
-    /**
-     * Sorts the rows, then inserts sections between CRS instances that use different datums.
-     */
-    @Override
-    protected void sortRows() {
-        super.sortRows();
-        @SuppressWarnings("SuspiciousToArrayCall")
-        final ByName[] data = rows.toArray(new ByName[rows.size()]);
-        final Map<String,String> sections = new TreeMap<>();
-        for (final ByName row : data) {
-            final String section = row.section;
-            if (section != null) {
-                sections.put(CharSequences.toASCII(section).toString().toLowerCase(), section);
-            }
-        }
-        rows.clear();
-        /*
-         * Recopy the rows, but section-by-section. We do this sorting here instead of in the Row.compareTo(Row)
-         * method in order to preserve the alphabetical order of rows with unknown datum.
-         * Algorithm below is inefficient, but this class should be rarely used anyway and only by site maintainer.
-         */
-        for (final String section : sections.values()) {
-            final Row separator = new Row();
-            separator.isSectionHeader = true;
-            separator.name = section;
-            rows.add(separator);
-            boolean found = false;
-            for (int i=0; i<data.length; i++) {
-                final ByName row = data[i];
-                if (row != null) {
-                    if (row.section != null) {
-                        found = section.equals(row.section);
-                    }
-                    if (found) {
-                        rows.add(row);
-                        data[i] = null;
-                        found = true;
-                    }
-                }
-            }
-        }
-        boolean found = false;
-        for (final ByName row : data) {
-            if (row != null) {
-                if (!found) {
-                    final Row separator = new Row();
-                    separator.isSectionHeader = true;
-                    separator.name = "Unknown";
-                    rows.add(separator);
-                }
-                rows.add(row);
-                found = true;
-            }
-        }
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java b/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java
index 617af3a..b8ed337 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java
@@ -29,6 +29,7 @@
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.cs.AxisDirection;
@@ -87,7 +88,7 @@
      *
      * @since 0.6
      */
-    public static void assertOgcIdentifierEquals(final String expected, final Identifier actual) {
+    public static void assertOgcIdentifierEquals(final String expected, final ReferenceIdentifier actual) {
         assertNotNull(actual);
         assertEquals("code",       expected,      actual.getCode());
         assertEquals("codeSpace",  Constants.OGC, actual.getCodeSpace());
@@ -108,7 +109,7 @@
     public static void assertEpsgIdentifierEquals(final String expected, final Identifier actual) {
         assertNotNull(actual);
         assertEquals("code",       expected,        actual.getCode());
-        assertEquals("codeSpace",  Constants.EPSG,  actual.getCodeSpace());
+        assertEquals("codeSpace",  Constants.EPSG,  (actual instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) actual).getCodeSpace() : null);
         assertEquals("authority",  Constants.EPSG,  Citations.toCodeSpace(actual.getAuthority()));
         assertEquals("identifier", Constants.EPSG + Constants.DEFAULT_SEPARATOR + expected,
                 IdentifiedObjects.toString(actual));
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/ConsistencyTest.java b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/ConsistencyTest.java
index 437ca32..94e1127 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/ConsistencyTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/ConsistencyTest.java
@@ -113,7 +113,7 @@
      */
     @Test
     public void testCoordinateReferenceSystems() throws FactoryException {
-        assumeTrue("Extensive tests not enabled.", RUN_EXTENSIVE_TESTS);
+        assumeTrue(RUN_EXTENSIVE_TESTS);
         final WKTFormat v1  = new WKTFormat(null, null);
         final WKTFormat v1c = new WKTFormat(null, null);
         final WKTFormat v2  = new WKTFormat(null, null);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java
index e0035b3..c96c83d 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java
@@ -79,6 +79,8 @@
 
 import static org.apache.sis.test.Assert.*;
 
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
+
 
 /**
  * Tests XML (un)marshalling of a metadata object containing various elements
@@ -118,6 +120,18 @@
     }
 
     /**
+     * Creates a telephone number of the given type.
+     *
+     * @param type Either {@code "VOICE"}, {@code "FACSIMILE"} or {@code "SMS"}.
+     */
+    private static DefaultTelephone telephone(final String number, final String type) {
+        final DefaultTelephone tel = new DefaultTelephone();
+        tel.setNumber(number);
+        tel.setNumberType(UnsupportedCodeList.valueOf(type));
+        return tel;
+    }
+
+    /**
      * Programmatically creates the metadata to marshal, or to compare against the unmarshalled metadata.
      *
      * @return the hard-coded representation of {@value #XML_FILE} content.
@@ -142,8 +156,8 @@
             final DefaultContact contact = new DefaultContact(online);
             contact.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "IFREMER");
             contact.setPhones(Arrays.asList(
-                    new DefaultTelephone("+33 (0)2 xx.xx.xx.x6", TelephoneType.VOICE),
-                    new DefaultTelephone("+33 (0)2 xx.xx.xx.x4", TelephoneType.FACSIMILE)
+                    telephone("+33 (0)2 xx.xx.xx.x6", "VOICE"),
+                    telephone("+33 (0)2 xx.xx.xx.x4", "FACSIMILE")
             ));
             final DefaultAddress address = new DefaultAddress();
             address.setDeliveryPoints(singleton("Brest institute"));
@@ -171,8 +185,8 @@
                 online.setProtocol("http");
                 final DefaultContact contact = new DefaultContact(online);
                 contact.setPhones(Arrays.asList(
-                        new DefaultTelephone("+33 (0)4 xx.xx.xx.x5", TelephoneType.VOICE),
-                        new DefaultTelephone("+33 (0)4 xx.xx.xx.x8", TelephoneType.FACSIMILE)
+                        telephone("+33 (0)4 xx.xx.xx.x5", "VOICE"),
+                        telephone("+33 (0)4 xx.xx.xx.x8", "FACSIMILE")
                 ));
                 final DefaultAddress address = new DefaultAddress();
                 address.setDeliveryPoints(singleton("Oceanology institute"));
@@ -222,7 +236,7 @@
              */
             {
                 final DefaultLegalConstraints constraint = new DefaultLegalConstraints();
-                constraint.setAccessConstraints(singleton(Restriction.LICENCE));
+                constraint.setAccessConstraints(singleton(Restriction.LICENSE));
                 identification.setResourceConstraints(singleton(constraint));
             }
             /*
@@ -230,14 +244,14 @@
              */
             {
                 @SuppressWarnings("deprecation")
-                final DefaultAssociatedResource aggregateInfo = new DefaultAggregateInformation();
+                final DefaultAggregateInformation aggregateInfo = new DefaultAggregateInformation();
                 final DefaultCitation name = new DefaultCitation("Some oceanographic campaign");
                 name.setAlternateTitles(singleton(new SimpleInternationalString("Pseudo group of data")));
                 name.setDates(singleton(new DefaultCitationDate(TestUtilities.date("1990-06-04 22:00:00"), DateType.REVISION)));
                 aggregateInfo.setName(name);
                 aggregateInfo.setInitiativeType(InitiativeType.CAMPAIGN);
-                aggregateInfo.setAssociationType(AssociationType.LARGER_WORK_CITATION);
-                identification.setAssociatedResources(singleton(aggregateInfo));
+                aggregateInfo.setAssociationType(AssociationType.LARGER_WORD_CITATION); // There is a typo ("WORD" → "WORK"), but we have to use the wrong spelling for this branch.
+                identification.setAggregationInfo(singleton(aggregateInfo));
             }
             /*
              * Data indentification / Extent.
@@ -391,6 +405,7 @@
                      "<gmx:Anchor xlink:href=\"SDN:L231:3:CDI\">Pseudo Common Data Index record</gmx:Anchor>");
         replace(xml, "<gcol:CharacterString>4326</gcol:CharacterString>",
                      "<gmx:Anchor xlink:href=\"SDN:L101:2:4326\">4326</gmx:Anchor>");
+        replace(xml, "License", "Licence");
         /*
          * The <gmd:EX_TemporalExtent> block can not be marshalled es expected yet (need a "sis-temporal" module).
          * We need to instruct the XML comparator to ignore this block during the comparison. We also ignore for
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataVerticalTest.java b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataVerticalTest.java
index 6a8809c..62696ff 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataVerticalTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataVerticalTest.java
@@ -18,7 +18,6 @@
 
 import java.net.URI;
 import java.util.Locale;
-import java.nio.charset.StandardCharsets;
 import javax.xml.bind.JAXBException;
 
 import org.opengis.metadata.*;
@@ -29,6 +28,7 @@
 import org.opengis.metadata.spatial.GeometricObjectType;
 import org.opengis.metadata.spatial.SpatialRepresentation;
 import org.opengis.metadata.spatial.VectorSpatialRepresentation;
+import org.opengis.metadata.identification.CharacterSet;
 import org.opengis.metadata.identification.DataIdentification;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.cs.AxisDirection;
@@ -100,10 +100,10 @@
     @Test
     public void testMetadataWithVerticalCRS() throws JAXBException {
         final Metadata metadata = unmarshalFile(Metadata.class, XML_FILE);
-        assertEquals("fileIdentifier", "20090901",                     metadata.getMetadataIdentifier().getCode());
-        assertEquals("language",       Locale.ENGLISH,                 getSingleton(metadata.getLocalesAndCharsets().keySet()));
-        assertEquals("characterSet",   StandardCharsets.UTF_8,         getSingleton(metadata.getLocalesAndCharsets().values()));
-        assertEquals("dateStamp",      xmlDate("2014-01-04 00:00:00"), getSingleton(metadata.getDateInfo()).getDate());
+        assertEquals("fileIdentifier", "20090901",                     metadata.getFileIdentifier());
+        assertEquals("language",       Locale.ENGLISH,                 metadata.getLanguage());
+        assertEquals("characterSet",   CharacterSet.UTF_8,             metadata.getCharacterSet());
+        assertEquals("dateStamp",      xmlDate("2014-01-04 00:00:00"), metadata.getDateStamp());
         /*
          * <gmd:contact>
          *   <gmd:CI_ResponsibleParty>
@@ -111,13 +111,10 @@
          *   </gmd:CI_ResponsibleParty>
          * </gmd:contact>
          */
-        final Responsibility contact        = getSingleton(metadata   .getContacts());
-        final Party          party          = getSingleton(contact    .getParties());
-        final Contact        contactInfo    = getSingleton(party      .getContactInfo());
-        final OnlineResource onlineResource = getSingleton(contactInfo.getOnlineResources());
-        assertInstanceOf("party", Organisation.class, party);
+        final ResponsibleParty contact = getSingleton(metadata.getContacts());
+        final OnlineResource onlineResource = contact.getContactInfo().getOnlineResource();
         assertNotNull("onlineResource", onlineResource);
-        assertEquals("organisationName", "Apache SIS", party.getName().toString());
+        assertEquals("organisationName", "Apache SIS", contact.getOrganisationName().toString());
         assertEquals("linkage", URI.create("http://sis.apache.org"), onlineResource.getLinkage());
         assertEquals("function", OnLineFunction.INFORMATION, onlineResource.getFunction());
         assertEquals("role", Role.PRINCIPAL_INVESTIGATOR, contact.getRole());
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 2e5bbe5..2119fdf 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
@@ -217,31 +217,14 @@
 
     // Direct (not from authority codes) geodetic object creations.
     org.apache.sis.referencing.StandardDefinitionsTest.class,
-    org.apache.sis.referencing.factory.GeodeticObjectFactoryTest.class,
-    org.apache.sis.referencing.factory.GIGS3002.class,
-    org.apache.sis.referencing.factory.GIGS3003.class,
-    org.apache.sis.referencing.factory.GIGS3004.class,
-    org.apache.sis.referencing.factory.GIGS3005.class,
 
     // Well Known Text parsing require above factory.
     org.apache.sis.io.wkt.MathTransformParserTest.class,
     org.apache.sis.io.wkt.GeodeticObjectParserTest.class,
     org.apache.sis.io.wkt.WKTFormatTest.class,
-    org.apache.sis.io.wkt.WKTParserTest.class,
     org.apache.sis.io.wkt.WKTDictionaryTest.class,
     org.apache.sis.io.wkt.ComparisonWithEPSG.class,
 
-    // Geodetic object creations from authority codes.
-    org.apache.sis.referencing.factory.GIGS2001.class,
-    org.apache.sis.referencing.factory.GIGS2002.class,
-    org.apache.sis.referencing.factory.GIGS2003.class,
-    org.apache.sis.referencing.factory.GIGS2004.class,
-    org.apache.sis.referencing.factory.GIGS2005.class,
-    org.apache.sis.referencing.factory.GIGS2006.class,
-    org.apache.sis.referencing.factory.GIGS2007.class,
-    org.apache.sis.referencing.factory.GIGS2008.class,
-    org.apache.sis.referencing.factory.GIGS2009.class,
-
     // Following tests may use indirectly EPSG factory.
     org.apache.sis.referencing.CommonCRSTest.class,
     org.apache.sis.referencing.factory.CommonAuthorityFactoryTest.class,
@@ -271,10 +254,6 @@
     org.apache.sis.referencing.operation.builder.LocalizationGridBuilderTest.class,
     org.apache.sis.referencing.operation.builder.LinearizerTest.class,
 
-    // GeoAPI conformance test suite.
-    org.apache.sis.referencing.geoapi.AuthorityFactoryTest.class,
-    org.apache.sis.referencing.geoapi.ParameterizedTransformTest.class,
-
     // Geometry and miscellaneous
     org.apache.sis.geometry.AbstractDirectPositionTest.class,
     org.apache.sis.geometry.GeneralDirectPositionTest.class,
diff --git a/core/sis-utility/pom.xml b/core/sis-utility/pom.xml
index 7af5fa4..01cd8e1 100644
--- a/core/sis-utility/pom.xml
+++ b/core/sis-utility/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Duration.java
similarity index 61%
rename from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
rename to core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Duration.java
index 2984463..0da87d2 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Duration.java
@@ -14,24 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.temporal;
 
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
  *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   1.0
+ * @version 1.0
  * @module
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public interface Duration {
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Instant.java
similarity index 61%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Instant.java
index 2984463..307dada 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Instant.java
@@ -14,24 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.temporal;
 
-import org.opengis.style.Symbolizer;
+import java.util.Date;
+import org.opengis.temporal.TemporalPrimitive;
+
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
  *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.5
  * @module
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public interface Instant extends TemporalPrimitive {
+    /**
+     * Gets the date of this instant.
+     *
+     * @return The date.
+     */
+    Date getDate();
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Period.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Period.java
new file mode 100644
index 0000000..5212fe4
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Period.java
@@ -0,0 +1,44 @@
+/*
+ * 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.geoapi.temporal;
+
+import org.opengis.temporal.TemporalPrimitive;
+
+
+/**
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public interface Period extends TemporalPrimitive {
+    /**
+     * Links this period to the instant at which it ends.
+     *
+     * @return The beginning instant.
+     */
+    Instant getBeginning();
+
+    /**
+     * Links this period to the instant at which it ends.
+     *
+     * @return The end instant.
+     */
+    Instant getEnding();
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/PeriodDuration.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/PeriodDuration.java
new file mode 100644
index 0000000..b417b85
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/PeriodDuration.java
@@ -0,0 +1,69 @@
+/*
+ * 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.geoapi.temporal;
+
+import org.opengis.util.InternationalString;
+
+
+/**
+ * Placeholder for a GeoAPI interfaces which is still incomplete in GeoAPI 3.0.
+ * We reproduce here the GeoAPI 3.1-pending API. Note that at the time of writing,
+ * this is a bad API (values shall not be instances of {@link InternationalString}).
+ * This will be fixed in a future GeoAPI version.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public interface PeriodDuration extends org.opengis.temporal.PeriodDuration {
+    /**
+     * A positive integer, followed by the character "Y",
+     * which indicated the number of years in the period.
+     */
+    InternationalString getYears();
+
+    /**
+     * A positive integer, followed by the character "M",
+     * which indicated the number of months in the period.
+     */
+    InternationalString getMonths();
+
+    /**
+     * A positive integer, followed by the character "D",
+     * which indicated the number of days in the period.
+     */
+    InternationalString getDays();
+
+    /**
+     * A positive integer, followed by the character "H",
+     * which indicated the number of hours in the period.
+     */
+    InternationalString getHours();
+
+    /**
+     * A positive integer, followed by the character "M",
+     * which indicated the number of minutes in the period.
+     */
+    InternationalString getMinutes();
+
+    /**
+     * A positive integer, followed by the character "S",
+     * which indicated the number of seconds in the period.
+     */
+    InternationalString getSeconds();
+}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Position.java
similarity index 61%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Position.java
index 2984463..a6bb000 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Position.java
@@ -14,24 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.internal.geoapi.temporal;
 
-import org.opengis.style.Symbolizer;
+import java.util.Date;
+
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
  *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
  * @module
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
+public interface Position {
+    /**
+     * Returns the time value as a {@code Date} object.
+     *
+     * @return The temporal value.
+     */
+    Date getDate();
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/TemporalFactory.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/TemporalFactory.java
new file mode 100644
index 0000000..e062bd0
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/TemporalFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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.geoapi.temporal;
+
+import java.util.Date;
+import org.opengis.util.InternationalString;
+
+
+/**
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public interface TemporalFactory {
+    Instant createInstant(Date date);
+
+    Period createPeriod(Instant begin, Instant end);
+
+    PeriodDuration createPeriodDuration(InternationalString years, InternationalString months,
+            InternationalString week, InternationalString days, InternationalString hours,
+            InternationalString minutes, InternationalString seconds);
+}
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/package-info.java
similarity index 61%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
copy to core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/package-info.java
index 2984463..61f8d32 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/ResourceSymbolizer.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/package-info.java
@@ -14,24 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
-
-import org.opengis.style.Symbolizer;
 
 /**
- * Resource symbolizers act on a resource as a whole, not on individual features.
- * Such symbolizers are not defined by the Symbology Encoding specification but are
- * often required to produce uncommon presentations.
+ * Placeholder for GeoAPI interfaces not present in GeoAPI 3.0.
  *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
+ * <STRONG>Do not use!</STRONG>
  *
- * @author  Johann Sorel (Geomatys)
- * @version 1.2
- * @since   1.2
+ * This package is for internal use by SIS only. Classes in this package
+ * may change in incompatible ways in any future version without notice.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
  * @module
  */
-public interface ResourceSymbolizer extends Symbolizer {
-
-}
+package org.apache.sis.internal.geoapi.temporal;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java
index 8235270..dd2060c 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java
@@ -119,7 +119,7 @@
      *
      * @see org.apache.sis.util.Version
      */
-    public static final int MINOR_VERSION = 9;
+    public static final int MINOR_VERSION = 3;
 
     /**
      * The prefix of all classnames in Apache SIS, including a trailing dot.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
index 8d87e58..21d9819 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
@@ -60,7 +60,7 @@
      * Whatever JMX agent is enabled. Setting this variable to {@code false} allows the
      * Java compiler to omit any dependency to this {@code Supervisor} class.
      */
-    static final boolean ENABLED = true;
+    static final boolean ENABLED = false;
 
     /**
      * The JMX object name for the {@code Supervisor} service.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultInstant.java b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultInstant.java
index b312280..4555787 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultInstant.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultInstant.java
@@ -17,8 +17,7 @@
 package org.apache.sis.internal.temporal;
 
 import java.util.Date;
-import org.opengis.temporal.Instant;
-import org.opengis.temporal.TemporalPosition;
+import org.apache.sis.internal.geoapi.temporal.Instant;
 
 
 /**
@@ -30,7 +29,7 @@
  * @since   1.2
  * @module
  */
-final class DefaultInstant extends Primitive implements Instant {
+final class DefaultInstant implements Instant {
     /** The date in milliseconds since epoch. */
     private final long millis;
 
@@ -44,11 +43,6 @@
         return new Date(millis);
     }
 
-    /** Association to a temporal reference system. */
-    @Override public TemporalPosition getTemporalPosition() {
-        throw DefaultTemporalFactory.unsupported();
-    }
-
     /** String representation in ISO format. */
     @Override public String toString() {
         return java.time.Instant.ofEpochMilli(millis).toString();
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultPeriod.java b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultPeriod.java
index 353d644..5682efb 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultPeriod.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultPeriod.java
@@ -17,8 +17,8 @@
 package org.apache.sis.internal.temporal;
 
 import java.util.Objects;
-import org.opengis.temporal.Instant;
-import org.opengis.temporal.Period;
+import org.apache.sis.internal.geoapi.temporal.Instant;
+import org.apache.sis.internal.geoapi.temporal.Period;
 
 
 /**
@@ -30,7 +30,7 @@
  * @since   1.2
  * @module
  */
-final class DefaultPeriod extends Primitive implements Period {
+final class DefaultPeriod implements Period {
     /** Bounds making this period. */
     private final Instant beginning, ending;
 
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultPeriodDuration.java b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultPeriodDuration.java
index 052a12c..b6620b8 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultPeriodDuration.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultPeriodDuration.java
@@ -17,7 +17,7 @@
 package org.apache.sis.internal.temporal;
 
 import java.util.Objects;
-import org.opengis.temporal.PeriodDuration;
+import org.apache.sis.internal.geoapi.temporal.PeriodDuration;
 import org.opengis.util.InternationalString;
 
 
@@ -52,11 +52,9 @@
     }
 
 
-    @Override public InternationalString getDesignator()    {return null;}
     @Override public InternationalString getYears()         {return years;}
     @Override public InternationalString getMonths()        {return months;}
     @Override public InternationalString getDays()          {return days;}
-    @Override public InternationalString getTimeIndicator() {return null;}
     @Override public InternationalString getHours()         {return hours;}
     @Override public InternationalString getMinutes()       {return minutes;}
     @Override public InternationalString getSeconds()       {return seconds;}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java
index d040aea..184c20c 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java
@@ -17,14 +17,8 @@
 package org.apache.sis.internal.temporal;
 
 import java.util.Date;
-import java.util.Collection;
-import javax.measure.Unit;
-import javax.measure.quantity.Time;
-import org.opengis.temporal.*;
-import org.opengis.metadata.Identifier;
-import org.opengis.metadata.extent.Extent;
 import org.opengis.util.InternationalString;
-import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.geoapi.temporal.*;
 
 
 /**
@@ -62,89 +56,4 @@
     {
         return new DefaultPeriodDuration(years, months, week, days, hours, minutes, seconds);
     }
-
-    /** Returns the exception to be thrown by all unsupported methods. */
-    static UnsupportedOperationException unsupported() {
-        return new UnsupportedOperationException(Errors.format(Errors.Keys.MissingRequiredModule_1, "sis-temporal"));
-    }
-
-    /** Unsupported. */
-    @Override public Calendar createCalendar(Identifier name, Extent domainOfValidity) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public Calendar createCalendar(Identifier name, Extent domainOfValidity, Collection<CalendarEra> referenceFrame, Clock timeBasis) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public CalendarDate createCalendarDate(TemporalReferenceSystem frame, IndeterminateValue indeterminatePosition, InternationalString calendarEraName, int[] calendarDate) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public CalendarEra createCalendarEra(InternationalString name, InternationalString referenceEvent, CalendarDate referenceDate, JulianDate julianReference, Period epochOfUse) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public Clock createClock(Identifier name, Extent domainOfValidity, InternationalString referenceEvent, ClockTime referenceTime, ClockTime utcReference) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public ClockTime createClockTime(TemporalReferenceSystem frame, IndeterminateValue indeterminatePosition, Number[] clockTime) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public DateAndTime createDateAndTime(TemporalReferenceSystem frame, IndeterminateValue indeterminatePosition, InternationalString calendarEraName, int[] calendarDate, Number[] clockTime) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public IntervalLength createIntervalLenght(Unit unit, int radix, int factor, int value) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public JulianDate createJulianDate(TemporalReferenceSystem frame, IndeterminateValue indeterminatePosition, Number coordinateValue) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public OrdinalEra createOrdinalEra(InternationalString name, Date beginning, Date end, Collection<OrdinalEra> member) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public OrdinalPosition createOrdinalPosition(TemporalReferenceSystem frame, IndeterminateValue indeterminatePosition, OrdinalEra ordinalPosition) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public OrdinalReferenceSystem createOrdinalReferenceSystem(Identifier name, Extent domainOfValidity, Collection<OrdinalEra> ordinalEraSequence) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public TemporalCoordinate createTemporalCoordinate(TemporalReferenceSystem frame, IndeterminateValue indeterminatePosition, Number coordinateValue) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public TemporalCoordinateSystem createTemporalCoordinateSystem(Identifier name, Extent domainOfValidity, Date origin, Unit<Time> interval) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public TemporalPosition createTemporalPosition(TemporalReferenceSystem frame, IndeterminateValue indeterminatePosition) {
-        throw unsupported();
-    }
-
-    /** Unsupported. */
-    @Override public TemporalReferenceSystem createTemporalReferenceSystem(Identifier name, Extent domainOfValidity) {
-        throw unsupported();
-    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/Primitive.java b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/Primitive.java
deleted file mode 100644
index 20c6100..0000000
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/Primitive.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.temporal;
-
-import org.opengis.temporal.Duration;
-import org.opengis.temporal.RelativePosition;
-import org.opengis.temporal.TemporalGeometricPrimitive;
-import org.opengis.temporal.TemporalPrimitive;
-import org.opengis.referencing.ReferenceIdentifier;
-
-
-/**
- * Base implementation of GeoAPI temporal primitives. This is a temporary class;
- * GeoAPI temporal interfaces are expected to change a lot in a future revision.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
- * @since   1.2
- * @module
- */
-class Primitive implements TemporalGeometricPrimitive, ReferenceIdentifier {
-    /**
-     * For sub-class constructors.
-     */
-    Primitive() {
-    }
-
-    /**
-     * The primary name by which this object is identified.
-     * This field is inherited from ISO 19111 {@code IdentifiedObject} and is in principle mandatory.
-     */
-    @Override
-    public final ReferenceIdentifier getName() {
-        return this;
-    }
-
-    /**
-     * Returns the string representation as the code for this object. This is not a correct identifier code,
-     * but we use that as a trick for forcing {@link org.apache.sis.util.collection.TreeTableFormat} to show
-     * the temporal value, because the formatter handles {@link org.opengis.referencing.IdentifiedObject} in
-     * a special way.
-     */
-    @Override public final String getCode() {
-        return toString();
-    }
-
-    /** position of this primitive relative to another primitive. */
-    @Override public final RelativePosition relativePosition(TemporalPrimitive other) {
-        throw DefaultTemporalFactory.unsupported();
-    }
-
-    /** Absolute value of the difference between temporal positions. */
-    @Override public final Duration distance(TemporalGeometricPrimitive other) {
-        throw DefaultTemporalFactory.unsupported();
-    }
-
-    /** Duration of this temporal geometric primitive. */
-    @Override public final Duration length() {
-        return null;    // Do not throw an exception here; this is invoked by reflection.
-    }
-}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CodeLists.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CodeLists.java
index ee9d73d..02d119a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CodeLists.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CodeLists.java
@@ -19,25 +19,22 @@
 import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.UndeclaredThrowableException;
-import java.util.function.Predicate;
 import org.opengis.util.CodeList;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Characters.Filter;
 
-// Branch-dependent imports
-import org.opengis.util.ControlledVocabulary;
-
 
 /**
  * Implementation of some {@link org.apache.sis.util.iso.Types} methods needed by {@code sis-utility} module.
- * This class opportunistically implements {@link Predicate} interface, but this is an implementation details.
+ * This class opportunistically implements {@link CodeList.Filter} interface, but this should be considered
+ * an implementation details.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
  */
-public final class CodeLists implements Predicate<CodeList<?>> {
+public final class CodeLists implements CodeList.Filter {
     /**
      * The name of bundle resources for code list titles. The resources should be loaded with
      * the same class loader than {@code org.opengis.annotation.UML.class.getClassLoader()}.
@@ -51,10 +48,26 @@
     private final String codename;
 
     /**
+     * {@code true} if {@link CodeList#valueOf} is allowed to create new code lists.
+     */
+    private final boolean canCreate;
+
+    /**
      * Creates a new filter for the specified code name.
      */
-    private CodeLists(final String codename) {
+    private CodeLists(final String codename, final boolean canCreate) {
         this.codename  = codename;
+        this.canCreate = canCreate;
+    }
+
+    /**
+     * Returns the name of the code to create, or {@code null} if no new code list shall be created.
+     *
+     * @return the name specified at construction time.
+     */
+    @Override
+    public String codename() {
+        return canCreate ? codename : null;
     }
 
     /**
@@ -63,7 +76,7 @@
      * @param  code  the code list candidate.
      */
     @Override
-    public boolean test(final CodeList<?> code) {
+    public boolean accept(final CodeList<?> code) {
         for (final String candidate : code.names()) {
             if (accept(candidate, codename)) {
                 return true;
@@ -98,7 +111,7 @@
         if (name == null || name.isEmpty()) {
             return null;
         }
-        return CodeList.valueOf(codeType, new CodeLists(name), canCreate ? name : null);
+        return CodeList.valueOf(codeType, new CodeLists(name, canCreate));
     }
 
     /**
@@ -120,19 +133,9 @@
             if (values == null) {
                 throw e;
             }
-            if (values instanceof ControlledVocabulary[]) {
-                for (final ControlledVocabulary code : (ControlledVocabulary[]) values) {
-                    for (final String candidate : code.names()) {
-                        if (accept(candidate, name)) {
-                            return enumType.cast(code);
-                        }
-                    }
-                }
-            } else {
-                for (final Enum<?> code : values) {
-                    if (accept(code.name(), name)) {
-                        return enumType.cast(code);
-                    }
+            for (final Enum<?> code : values) {
+                if (accept(code.name(), name)) {
+                    return enumType.cast(code);
                 }
             }
         }
@@ -149,7 +152,7 @@
      * @see org.apache.sis.util.iso.Types#getCodeValues(Class)
      */
     @SuppressWarnings("unchecked")
-    public static <T extends ControlledVocabulary> T[] values(final Class<T> codeType) {
+    public static <T extends CodeList<?>> T[] values(final Class<T> codeType) {
         Object values;
         try {
             values = codeType.getMethod("values", (Class<?>[]) null).invoke(null, (Object[]) null);
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/MetadataServices.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/MetadataServices.java
index 5975d01..ae93f51 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/MetadataServices.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/MetadataServices.java
@@ -31,7 +31,7 @@
 import org.apache.sis.util.CharSequences;
 
 // Branch-dependent imports
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 
 
 /**
@@ -122,9 +122,9 @@
      * @param  locale  desired locale for the title.
      * @return the title.
      *
-     * @see org.apache.sis.util.iso.Types#getCodeTitle(ControlledVocabulary)
+     * @see org.apache.sis.util.iso.Types#getCodeTitle(CodeList)
      */
-    public String getCodeTitle(final ControlledVocabulary code, final Locale locale) {
+    public String getCodeTitle(final CodeList<?> code, final Locale locale) {
         /*
          * Following code reproduces the work done by org.apache.sis.util.iso.Types.getCodeList(…)
          * with less handling of special cases. It is executed only if the sis-metadata module is
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/PropertyFormat.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/PropertyFormat.java
index e8595a4..4ddecad 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/PropertyFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/PropertyFormat.java
@@ -28,7 +28,7 @@
 import org.opengis.util.Record;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 import org.apache.sis.io.CompoundFormat;
 import org.apache.sis.io.LineAppender;
 import org.apache.sis.util.CharSequences;
@@ -100,12 +100,12 @@
             text = freeText(((InternationalString) value).toString(getLocale()));
         } else if (value instanceof CharSequence) {
             text = freeText(value.toString());
-        } else if (value instanceof ControlledVocabulary) {
-            text = MetadataServices.getInstance().getCodeTitle((ControlledVocabulary) value, getLocale());
-        } else if (value instanceof Boolean) {
-            text = Vocabulary.getResources(getLocale()).getString((Boolean) value ? Vocabulary.Keys.True : Vocabulary.Keys.False);
+        } else if (value instanceof CodeList<?>) {
+            text = MetadataServices.getInstance().getCodeTitle((CodeList<?>) value, getLocale());
         } else if (value instanceof Enum<?>) {
             text = CharSequences.upperCaseToSentence(((Enum<?>) value).name());
+        } else if (value instanceof Boolean) {
+            text = Vocabulary.getResources(getLocale()).getString((Boolean) value ? Vocabulary.Keys.True : Vocabulary.Keys.False);
         } else if (value instanceof Type) {
             appendName(((Type) value).getTypeName());
             return;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
index d3cdfb1..8b80a21 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
@@ -17,9 +17,9 @@
 package org.apache.sis.internal.util;
 
 import java.util.Date;
-import org.opengis.temporal.Instant;
-import org.opengis.temporal.Period;
-import org.opengis.temporal.TemporalFactory;
+import org.apache.sis.internal.geoapi.temporal.Instant;
+import org.apache.sis.internal.geoapi.temporal.Period;
+import org.apache.sis.internal.geoapi.temporal.TemporalFactory;
 import org.opengis.temporal.TemporalPrimitive;
 import org.apache.sis.util.Static;
 import org.apache.sis.internal.system.DefaultFactories;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/io/IdentifiedObjectFormat.java b/core/sis-utility/src/main/java/org/apache/sis/io/IdentifiedObjectFormat.java
index 1aba7c4..a67d865 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/io/IdentifiedObjectFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/io/IdentifiedObjectFormat.java
@@ -21,8 +21,8 @@
 import java.text.FieldPosition;
 import java.text.ParsePosition;
 import org.opengis.util.GenericName;
-import org.opengis.metadata.Identifier;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.MetadataServices;
@@ -60,7 +60,7 @@
      */
     @Override
     public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
-        final Identifier identifier = ((IdentifiedObject) obj).getName();
+        final ReferenceIdentifier identifier = ((IdentifiedObject) obj).getName();
         if (identifier == null) {
             return toAppendTo.append(Vocabulary.getResources(locale).getString(Vocabulary.Keys.Unnamed));
         }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/io/LineAppender.java b/core/sis-utility/src/main/java/org/apache/sis/io/LineAppender.java
index eb7765a..7e04681 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/io/LineAppender.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/io/LineAppender.java
@@ -279,7 +279,6 @@
      *
      * @param  lineSeparator  the new line separator, or {@code null} for forwarding EOL <i>as-is</i>.
      *
-     * @see System#lineSeparator()
      * @see Characters#isLineOrParagraphSeparator(int)
      */
     public void setLineSeparator(final String lineSeparator) {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/io/TabularFormat.java b/core/sis-utility/src/main/java/org/apache/sis/io/TabularFormat.java
index 726f748..bbf262f 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/io/TabularFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/io/TabularFormat.java
@@ -214,7 +214,7 @@
      *   <li>If present, {@code '?'} shall be the first character in the pattern.</li>
      *   <li>The repeated character (specified inside the pair of brackets) is mandatory.</li>
      *   <li>In the current implementation, the repeated character must be in the
-     *       {@linkplain Character#isBmpCodePoint(int) Basic Multilanguage Plane}.</li>
+     *       Basic Multilanguage Plane.</li>
      *   <li>If {@code '/'} is present, anything on its right side shall be compliant
      *       with the {@link Pattern} syntax.</li>
      * </ul>
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
index 0cdee86..46d5ad7 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
@@ -233,7 +233,7 @@
      * returns directly the {@linkplain Math#abs(double) absolute value} of that element
      * without computing {@code sqrt(v²)}, in order to avoid rounding error. This special case
      * has been implemented because this method is often invoked for computing the length of
-     * {@linkplain org.opengis.coverage.grid.RectifiedGrid#getOffsetVectors() offset vectors},
+     * offset vectors,
      * typically aligned with the axes of a {@linkplain org.opengis.referencing.cs.CartesianCS
      * Cartesian coordinate system}.
      *
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java b/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
index a42be30..fab2a63 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
@@ -258,13 +258,6 @@
      * instead of {@code MeasurementRange}. Nevertheless this method may return {@code null} if a unit
      * <em>should</em> exist but for some reason is unavailable.
      *
-     * <div class="note"><b>Example:</b>
-     * ISO 19115-1 {@code SampleDimension} specifies that its
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getUnits() unit} property is mandatory if the
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getMinValue() minimum value} or
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getMaxValue() maximum value} are provided.
-     * Nevertheless it happens sometime that this information is missing in metadata.</div>
-     *
      * @return the unit of measurement, or {@code null}.
      */
     @Override
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
index 6c55339..ced02b6 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
@@ -64,8 +64,8 @@
  * <a href="https://en.wikipedia.org/wiki/Interval_%28mathematics%29">mathematical definition of interval</a>.
  * It is closely related, while not identical, to the ISO 19123 (<cite>Coverage geometry and functions</cite>)
  * definition of "ranges". At the difference of the parent {@link Range} class, which can be used only with
- * {@linkplain org.opengis.coverage.DiscreteCoverage discrete coverages}, the {@code NumberRange} class can
- * also be used with {@linkplain org.opengis.coverage.ContinuousCoverage continuous coverages}.
+ * discrete coverages, the {@code NumberRange} class can
+ * also be used with continuous coverages.
  *
  * <h2>Immutability and thread safety</h2>
  * This class and the {@link MeasurementRange} subclasses are immutable, and thus inherently thread-safe.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java b/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
index 6ba1731..03128ca 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
@@ -56,8 +56,8 @@
  *
  * <h2>Relationship with ISO 19123 definition of range</h2>
  * The ISO 19123 standard (<cite>Coverage geometry and functions</cite>) defines the range as the set
- * (either finite or {@linkplain org.opengis.geometry.TransfiniteSet transfinite}) of feature attribute
- * values associated by a function (the {@linkplain org.opengis.coverage.Coverage coverage}) with the
+ * (either finite or transfinite) of feature attribute
+ * values associated by a function (the coverage) with the
  * elements of the coverage domain. In other words, if we see a coverage as a function, then a range
  * is the set of possible return values.
  *
@@ -68,9 +68,8 @@
  * is closely related, but not identical, to the ISO 19123 definition or range.</p>
  *
  * <p>Ranges are not necessarily numeric. Numeric and non-numeric ranges can be associated to
- * {@linkplain org.opengis.coverage.DiscreteCoverage discrete coverages}, while typically only
- * numeric ranges can be associated to {@linkplain org.opengis.coverage.ContinuousCoverage
- * continuous coverages}.</p>
+ * discrete coverages, while typically only
+ * numeric ranges can be associated to continuous coverages.</p>
  *
  * <h2>Immutability and thread safety</h2>
  * This class and the {@link NumberRange} / {@link MeasurementRange} subclasses are immutable,
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/AbstractInternationalString.java b/core/sis-utility/src/main/java/org/apache/sis/util/AbstractInternationalString.java
index 8db84a3..174015a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/AbstractInternationalString.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/AbstractInternationalString.java
@@ -38,7 +38,7 @@
  * by a {@link org.opengis.util.CodeList} value. This can be done with:
  *
  * <ul>
- *   <li>{@link org.apache.sis.util.iso.Types#getCodeTitle(ControlledVocabulary)} for getting
+ *   <li>{@link org.apache.sis.util.iso.Types#getCodeTitle(CodeList)} for getting
  *       the {@link InternationalString} instance to store in a metadata property.</li>
  *   <li>{@link org.apache.sis.util.iso.Types#forCodeTitle(CharSequence)} for retrieving the
  *       {@link org.opengis.util.CodeList} previously stored as an {@code InternationalString}.</li>
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java b/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
index 10f6d75..1bc49aa 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
@@ -28,9 +28,6 @@
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.util.resources.Errors;
 
-// Branch-specific dependencies
-import org.opengis.coverage.grid.GridEnvelope;
-
 
 /**
  * Static methods for performing argument checks.
@@ -877,30 +874,6 @@
     }
 
     /**
-     * Ensures that the given grid envelope, if non-null, has the expected number of dimensions.
-     * This method does nothing if the given grid envelope is null.
-     *
-     * @param  name      the name of the argument to be checked. Used only if an exception is thrown.
-     * @param  expected  the expected number of dimensions.
-     * @param  envelope  the grid envelope to check for its dimension, or {@code null}.
-     * @throws MismatchedDimensionException if the given envelope is non-null and does
-     *         not have the expected number of dimensions.
-     *
-     * @since 1.3
-     */
-    public static void ensureDimensionMatches(final String name, final int expected, final GridEnvelope envelope)
-            throws MismatchedDimensionException
-    {
-        if (envelope != null) {
-            final int dimension = envelope.getDimension();
-            if (dimension != expected) {
-                throw new MismatchedDimensionException(Errors.format(
-                        Errors.Keys.MismatchedDimension_3, name, expected, dimension));
-            }
-        }
-    }
-
-    /**
      * Ensures that the given transform, if non-null, has the expected number of source and target dimensions.
      * This method does nothing if the given transform is null.
      *
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
index 1a3aef3..4b905a1 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
@@ -32,7 +32,6 @@
 import java.lang.reflect.Modifier;
 import javax.measure.Unit;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
@@ -426,8 +425,8 @@
                 replacement = message;
             } else if (element instanceof Class<?>) {
                 replacement = Classes.getShortName(getPublicType((Class<?>) element));
-            } else if (element instanceof ControlledVocabulary) {
-                replacement = MetadataServices.getInstance().getCodeTitle((ControlledVocabulary) element, getLocale());
+            } else if (element instanceof CodeList<?>) {
+                replacement = MetadataServices.getInstance().getCodeTitle((CodeList<?>) element, getLocale());
             } else if (element instanceof Range<?>) {
                 final Range<?> range = (Range<?>) element;
                 replacement = new RangeFormat(getLocale(), range.getElementType()).format(range);
diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java b/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java
index fc4fb73..4708536 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java
@@ -48,7 +48,7 @@
  * @since   0.3
  * @module
  */
-public strictfp class Assert extends org.opengis.test.Assert {
+public strictfp class Assert extends GeoapiAssert {
     /**
      * For subclass constructor only.
      */
diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/ContentVerifier.java b/core/sis-utility/src/test/java/org/apache/sis/test/ContentVerifier.java
new file mode 100644
index 0000000..51c5362
--- /dev/null
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/ContentVerifier.java
@@ -0,0 +1,40 @@
+/*
+ * 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.test;
+
+import static org.junit.Assume.assumeTrue;
+
+import org.opengis.metadata.Metadata;
+
+
+/**
+ * Place-holder for a GeoAPI 3.1 class. Used only for allowing the code to compile.
+ * For real test execution, see the development branches on GeoAPI 4.0-SNAPSHOT.
+ */
+public class ContentVerifier {
+    public void addPropertyToIgnore(Class<?> type, String property) {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+    }
+
+    public void addMetadataToVerify(Metadata actual) {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+    }
+
+    public void assertMetadataEquals(final String path, final Object value, final Object... others) {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+    }
+}
diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/GeoapiAssert.java b/core/sis-utility/src/test/java/org/apache/sis/test/GeoapiAssert.java
new file mode 100644
index 0000000..6914ff5
--- /dev/null
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/GeoapiAssert.java
@@ -0,0 +1,196 @@
+/*
+ * 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.test;
+
+import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.util.InternationalString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+
+/**
+ * Temporary class for test methods that are expected to be provided in next GeoAPI release.
+ * Those methods are defined in a separated class in order to make easier for us to identify
+ * which methods may be removed from SIS (actually moved to GeoAPI) in a future GeoAPI release.
+ *
+ * <p>This class is needed for Apache SIS trunk, since the later is linked to GeoAPI official release.
+ * But this class can be removed on Apache SIS branches which are linked to a GeoAPI development branch.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+strictfp class GeoapiAssert extends org.opengis.test.Assert {
+    /**
+     * A flag for code that are pending next GeoAPI release before to be enabled.
+     * This flag is always set to {@code false}, except occasionally just before
+     * a GeoAPI release for testing purpose. It shall be used as below:
+     *
+     * {@preformat java
+     *     if (PENDING_NEXT_GEOAPI_RELEASE) {
+     *         // Do some stuff here.
+     *     }
+     * }
+     *
+     * The intend is to make easier to identify test cases that fail with the current version
+     * of the {@code geoapi-conformance} module, but should pass with the development snapshot.
+     */
+    public static final boolean PENDING_NEXT_GEOAPI_RELEASE = false;
+
+    /**
+     * The keyword for unrestricted value in {@link String} arguments.
+     */
+    private static final String UNRESTRICTED = "##unrestricted";
+
+    /**
+     * For subclass constructor only.
+     */
+    GeoapiAssert() {
+    }
+
+    /**
+     * Returns the concatenation of the given message with the given extension.
+     * This method returns the given extension if the message is null or empty.
+     *
+     * <p>Invoking this method is equivalent to invoking {@code nonNull(message) + ext},
+     * but avoid the creation of temporary objects in the common case where the message
+     * is null.</p>
+     *
+     * @param  message The message, or {@code null}.
+     * @param  ext The extension to append after the message.
+     * @return The concatenated string.
+     */
+    private static String concat(String message, final String ext) {
+        if (message == null || (message = message.trim()).isEmpty()) {
+            return ext;
+        }
+        return message + ' ' + ext;
+    }
+
+    /**
+     * Verifies if we expected a null value, then returns {@code true} if the value is null as expected.
+     */
+    private static boolean isNull(final String message, final Object expected, final Object actual) {
+        final boolean isNull = (actual == null);
+        if (isNull != (expected == null)) {
+            fail(concat(message, isNull ? "Value is null." : "Expected null."));
+        }
+        return isNull;
+    }
+
+    /**
+     * Asserts that the title or an alternate title of the given citation is equal to the given string.
+     * This method is typically used for testing if a citation stands for the OGC, OGP or EPSG authority
+     * for instance. Such abbreviations are often declared as {@linkplain Citation#getAlternateTitles()
+     * alternate titles} rather than the main {@linkplain Citation#getTitle() title}, but this method
+     * tests both for safety.
+     *
+     * @param message  Header of the exception message in case of failure, or {@code null} if none.
+     * @param expected The expected title or alternate title.
+     * @param actual   The citation to test.
+     */
+    public static void assertAnyTitleEquals(final String message, final String expected, final Citation actual) {
+        if (isNull(message, expected, actual)) {
+            return;
+        }
+        InternationalString title = actual.getTitle();
+        if (title != null && expected.equals(title.toString())) {
+            return;
+        }
+        for (final InternationalString t : actual.getAlternateTitles()) {
+            if (expected.equals(t.toString())) {
+                return;
+            }
+        }
+        fail(concat(message, '"' + expected + "\" not found in title or alternate titles."));
+    }
+
+    /**
+     * Asserts that the given identifier is equals to the given authority, code space, version and code.
+     * If any of the above-cited properties is {@code ""##unrestricted"}, then it will not be verified.
+     * This flexibility is useful in the common case where a test accepts any {@code version} value.
+     *
+     * @param message    Header of the exception message in case of failure, or {@code null} if none.
+     * @param authority  The expected authority title or alternate title (may be {@code null}), or {@code "##unrestricted"}.
+     * @param codeSpace  The expected code space (may be {@code null}), or {@code "##unrestricted"}.
+     * @param version    The expected version    (may be {@code null}), or {@code "##unrestricted"}.
+     * @param code       The expected code value (may be {@code null}), or {@code "##unrestricted"}.
+     * @param actual     The identifier to test.
+     */
+    public static void assertIdentifierEquals(final String message, final String authority, final String codeSpace,
+            final String version, final String code, final ReferenceIdentifier actual)
+    {
+        if (actual == null) {
+            fail(concat(message, "Identifier is null"));
+        } else {
+            if (!UNRESTRICTED.equals(authority)) assertAnyTitleEquals(message,                      authority, actual.getAuthority());
+            if (!UNRESTRICTED.equals(codeSpace)) assertEquals (concat(message, "Wrong code space"), codeSpace, actual.getCodeSpace());
+            if (!UNRESTRICTED.equals(version))   assertEquals (concat(message, "Wrong version"),    version,   actual.getVersion());
+            if (!UNRESTRICTED.equals(code))      assertEquals (concat(message, "Wrong code"),       code,      actual.getCode());
+        }
+    }
+
+    /**
+     * Asserts that all axes in the given coordinate system are pointing toward the given directions, in the same order.
+     *
+     * @param message  Header of the exception message in case of failure, or {@code null} if none.
+     * @param cs       The coordinate system to test.
+     * @param expected The expected axis directions.
+     */
+    public static void assertAxisDirectionsEqual(String message,
+            final CoordinateSystem cs, final AxisDirection... expected)
+    {
+        assertEquals(concat(message, "Wrong coordinate system dimension."), expected.length, cs.getDimension());
+        message = concat(message, "Wrong axis direction.");
+        for (int i=0; i<expected.length; i++) {
+            assertEquals(message, expected[i], cs.getAxis(i).getDirection());
+        }
+    }
+
+    /**
+     * Asserts that the given matrix is equals to the expected one, up to the given tolerance value.
+     *
+     * @param message   Header of the exception message in case of failure, or {@code null} if none.
+     * @param expected  The expected matrix, which may be {@code null}.
+     * @param actual    The matrix to compare, or {@code null}.
+     * @param tolerance The tolerance threshold.
+     */
+    public static void assertMatrixEquals(final String message, final Matrix expected, final Matrix actual, final double tolerance) {
+        if (isNull(message, expected, actual)) {
+            return;
+        }
+        final int numRow = actual.getNumRow();
+        final int numCol = actual.getNumCol();
+        assertEquals("numRow", expected.getNumRow(), numRow);
+        assertEquals("numCol", expected.getNumCol(), numCol);
+        for (int j=0; j<numRow; j++) {
+            for (int i=0; i<numCol; i++) {
+                final double e = expected.getElement(j,i);
+                final double a = actual.getElement(j,i);
+                if (!(StrictMath.abs(e - a) <= tolerance) && Double.doubleToLongBits(a) != Double.doubleToLongBits(e)) {
+                    fail("Matrix.getElement(" + j + ", " + i + "): expected " + e + " but got " + a);
+                }
+            }
+        }
+    }
+}
diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/LoggingWatcher.java b/core/sis-utility/src/test/java/org/apache/sis/test/LoggingWatcher.java
index 0ed8601..72bab65 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/test/LoggingWatcher.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/LoggingWatcher.java
@@ -24,11 +24,13 @@
 import java.util.logging.Logger;
 import java.util.logging.LogRecord;
 import java.util.logging.SimpleFormatter;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
 
 import static org.junit.Assert.*;
 
+// Branch-specific imports
+import org.junit.rules.TestWatchman;
+import org.junit.runners.model.FrameworkMethod;
+
 
 /**
  * Watches the logs sent to the given logger.
@@ -61,7 +63,7 @@
  * @since   0.6
  * @module
  */
-public final strictfp class LoggingWatcher extends TestWatcher implements Filter {
+public final strictfp class LoggingWatcher extends TestWatchman implements Filter {
     /**
      * The logged messages.
      */
@@ -106,7 +108,7 @@
      * @see #isLoggable(LogRecord)
      */
     @Override
-    protected final void starting(final Description description) {
+    public final void starting(final FrameworkMethod description) {
         assertNull(logger.getFilter());
         logger.setFilter(this);
     }
@@ -118,7 +120,7 @@
      * @param  description  a description of the JUnit test that finished.
      */
     @Override
-    protected final void finished(final Description description) {
+    public final void finished(final FrameworkMethod description) {
         logger.setFilter(null);
     }
 
diff --git a/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java b/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java
index 7d608db..6cdb46c 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java
@@ -44,8 +44,6 @@
 import java.io.NotSerializableException;
 import java.io.Serializable;
 import java.awt.geom.Point2D;
-import org.opengis.util.InternationalString;
-import org.opengis.metadata.extent.Extent;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.cs.EllipsoidalCS;
@@ -137,10 +135,7 @@
     /**
      * Dummy class for {@link #testGetLeafInterfaces()}.
      */
-    private abstract static class T1 implements GeographicCRS {
-        @Override public InternationalString getScope() {return null;}
-        @Override public Extent getDomainOfValidity() {return null;}
-    }
+    private abstract static class T1 implements GeographicCRS {}
     private abstract static class T2 extends T1 implements SingleCRS, CoordinateOperation {}
     private abstract static class T3 extends T2 implements Transformation {}
 
diff --git a/core/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java b/core/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java
index 3ace760..1d91441 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java
@@ -255,7 +255,7 @@
         tf = new TreeTableFormat(Locale.FRENCH, null);
         assertMultilinesEquals(
                 "Root\n" +
-                "  ├─CodeList…… Point de contact\n" +
+                "  ├─CodeList…… Point of contact\n" + // Not yet localized.
                 "  ├─Enum……………… Half down\n" +                      // No localization provided.
                 "  └─i18n……………… Une phrase en français\n", tf.format(table));
 
diff --git a/ide-project/NetBeans/README.txt b/ide-project/NetBeans/README.txt
index a61c0c9..6724fc5 100644
--- a/ide-project/NetBeans/README.txt
+++ b/ide-project/NetBeans/README.txt
@@ -4,37 +4,6 @@
 
 
 ==============================================================================
-Installation
-==============================================================================
-The configuration provided in this directory requires a checkout of GeoAPI
-source code. The recommended installation steps is as below (from the root
-directory of all SIS-related projects):
-
-  mkdir SIS
-  git clone -b geoapi-4.0 https://gitbox.apache.org/repos/asf/sis.git SIS/dev
-  mkdir GeoAPI
-  git clone http://github.com/opengeospatial/geoapi GeoAPI/master
-
-Above commands should create the following directory structure:
-
-  +-- GeoAPI
-  |   +-- master
-  |       +-- README.md
-  |       +-- etc...
-  +-- SIS
-      +-- dev
-          +-- README
-          +-- etc...
-
-If a different directory layout is desired, this is possible provided that
-the following line is added to "nbproject/private/private.properties" file:
-
-  project.GeoAPI = <path to your GeoAPI checkout>/ide-project/NetBeans
-
-
-
-
-==============================================================================
 Recommendations for NetBeans project configuration changes
 ==============================================================================
 There is 3 important files that should be edited BY HAND for preserving user-
diff --git a/ide-project/NetBeans/build.xml b/ide-project/NetBeans/build.xml
index bbb1786..853102e 100644
--- a/ide-project/NetBeans/build.xml
+++ b/ide-project/NetBeans/build.xml
@@ -49,6 +49,11 @@
     before it can be built by the NetBeans IDE.
   -->
   <target name="-post-compile">
+    <copy todir="${build.classes.dir}/org/apache/sis/util/iso">
+      <fileset dir="${project.root}/core/sis-metadata/src/main/resources/org/apache/sis/util/iso">
+        <include name="class-index.properties"/>
+      </fileset>
+    </copy>
     <copy todir="${build.classes.dir}">
       <fileset dir="${project.root}/core/sis-utility/target/generated-resources">
         <include name="**/*.utf"/>
diff --git a/ide-project/NetBeans/nbproject/build-impl.xml b/ide-project/NetBeans/nbproject/build-impl.xml
index d0134b8..451e1a7 100644
--- a/ide-project/NetBeans/nbproject/build-impl.xml
+++ b/ide-project/NetBeans/nbproject/build-impl.xml
@@ -19,7 +19,7 @@
   - cleanup
 
         -->
-<project xmlns:if="ant:if" xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" xmlns:unless="ant:unless" basedir=".." default="default" name="Apache_SIS_on_GeoAPI_3.1-impl">
+<project xmlns:if="ant:if" xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" xmlns:unless="ant:unless" basedir=".." default="default" name="Apache_SIS_on_GeoAPI_3.0-impl">
     <fail message="Please build using Ant 1.8.0 or higher.">
         <condition>
             <not>
@@ -182,7 +182,6 @@
             <or>
                 <available file="${test.javafx.dir}"/>
                 <available file="${test.console.dir}"/>
-                <available file="${test.portrayal.dir}"/>
                 <available file="${test.earth-obs.dir}"/>
                 <available file="${test.geotiff.dir}"/>
                 <available file="${test.netcdf.dir}"/>
@@ -343,7 +342,6 @@
         <fail unless="src.jpn-profile.dir">Must set src.jpn-profile.dir</fail>
         <fail unless="test.javafx.dir">Must set test.javafx.dir</fail>
         <fail unless="test.console.dir">Must set test.console.dir</fail>
-        <fail unless="test.portrayal.dir">Must set test.portrayal.dir</fail>
         <fail unless="test.earth-obs.dir">Must set test.earth-obs.dir</fail>
         <fail unless="test.geotiff.dir">Must set test.geotiff.dir</fail>
         <fail unless="test.netcdf.dir">Must set test.netcdf.dir</fail>
@@ -662,9 +660,6 @@
                             <fileset dir="${test.console.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
                                 <filename name="@{testincludes}"/>
                             </fileset>
-                            <fileset dir="${test.portrayal.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
-                                <filename name="@{testincludes}"/>
-                            </fileset>
                             <fileset dir="${test.earth-obs.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
                                 <filename name="@{testincludes}"/>
                             </fileset>
@@ -736,9 +731,6 @@
                     <fileset dir="${test.console.dir}" excludes="@{excludes},**/*.xml,${excludes}" includes="@{includes}">
                         <filename name="@{testincludes}"/>
                     </fileset>
-                    <fileset dir="${test.portrayal.dir}" excludes="@{excludes},**/*.xml,${excludes}" includes="@{includes}">
-                        <filename name="@{testincludes}"/>
-                    </fileset>
                     <fileset dir="${test.earth-obs.dir}" excludes="@{excludes},**/*.xml,${excludes}" includes="@{includes}">
                         <filename name="@{testincludes}"/>
                     </fileset>
@@ -783,7 +775,7 @@
                     </fileset>
                 </union>
                 <taskdef classname="org.testng.TestNGAntTask" classpath="${run.test.classpath}" name="testng"/>
-                <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="Apache_SIS_on_GeoAPI_3.1" testname="TestNG tests" workingDir="${work.dir}">
+                <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="Apache_SIS_on_GeoAPI_3.0" testname="TestNG tests" workingDir="${work.dir}">
                     <xmlfileset dir="${build.test.classes.dir}" includes="@{testincludes}"/>
                     <propertyset>
                         <propertyref prefix="test-sys-prop."/>
@@ -880,7 +872,7 @@
                 <condition else="-testclass @{testClass}" property="test.class.or.method" value="-methods @{testClass}.@{testMethod}">
                     <isset property="test.method"/>
                 </condition>
-                <condition else="-suitename Apache_SIS_on_GeoAPI_3.1 -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
+                <condition else="-suitename Apache_SIS_on_GeoAPI_3.0 -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
                     <matches pattern=".*\.xml" string="@{testClass}"/>
                 </condition>
                 <delete dir="${build.test.results.dir}" quiet="true"/>
@@ -1221,7 +1213,7 @@
         <delete file="${built-jar.properties}" quiet="true"/>
     </target>
     <target if="already.built.jar.${basedir}" name="-warn-already-built-jar">
-        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.1 was already built"/>
+        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.0 was already built"/>
     </target>
     <target depends="init,-deps-jar-init" name="deps-jar" unless="no.deps">
         <mkdir dir="${build.dir}"/>
@@ -1834,14 +1826,14 @@
         <!-- You can override this target in the ../build.xml file. -->
     </target>
     <target depends="-init-source-module-properties" if="named.module.internal" name="-init-test-javac-module-properties-with-module">
-        <j2seproject3:modulename property="test.module.name" sourcepath="${test.javafx.dir}:${test.console.dir}:${test.portrayal.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}"/>
-        <condition else="${empty.dir}" property="javac.test.sourcepath" value="${test.javafx.dir}:${test.console.dir}:${test.portrayal.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}">
+        <j2seproject3:modulename property="test.module.name" sourcepath="${test.javafx.dir}:${test.console.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}"/>
+        <condition else="${empty.dir}" property="javac.test.sourcepath" value="${test.javafx.dir}:${test.console.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}">
             <and>
                 <isset property="test.module.name"/>
                 <length length="0" string="${test.module.name}" when="greater"/>
             </and>
         </condition>
-        <condition else="--patch-module ${module.name}=${test.javafx.dir}:${test.console.dir}:${test.portrayal.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir} --add-reads ${module.name}=ALL-UNNAMED" property="javac.test.compilerargs" value="--add-reads ${test.module.name}=ALL-UNNAMED">
+        <condition else="--patch-module ${module.name}=${test.javafx.dir}:${test.console.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir} --add-reads ${module.name}=ALL-UNNAMED" property="javac.test.compilerargs" value="--add-reads ${test.module.name}=ALL-UNNAMED">
             <and>
                 <isset property="test.module.name"/>
                 <length length="0" string="${test.module.name}" when="greater"/>
@@ -1882,10 +1874,10 @@
     </target>
     <target depends="-init-test-javac-module-properties-with-module,-init-test-module-properties-without-module" name="-init-test-module-properties"/>
     <target if="do.depend.true" name="-compile-test-depend">
-        <j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir="${test.javafx.dir}:${test.console.dir}:${test.portrayal.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}"/>
+        <j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir="${test.javafx.dir}:${test.console.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}"/>
     </target>
     <target depends="init,deps-jar,compile,-init-test-module-properties,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test">
-        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" modulepath="${javac.test.modulepath}" processorpath="${javac.test.processorpath}" sourcepath="${javac.test.sourcepath}" srcdir="${test.javafx.dir}:${test.console.dir}:${test.portrayal.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}">
+        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" modulepath="${javac.test.modulepath}" processorpath="${javac.test.processorpath}" sourcepath="${javac.test.sourcepath}" srcdir="${test.javafx.dir}:${test.console.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}">
             <customize>
                 <compilerarg line="${javac.test.compilerargs}"/>
             </customize>
@@ -1893,7 +1885,6 @@
         <copy todir="${build.test.classes.dir}">
             <fileset dir="${test.javafx.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${test.console.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
-            <fileset dir="${test.portrayal.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${test.earth-obs.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${test.geotiff.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${test.netcdf.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
@@ -1922,7 +1913,7 @@
     <target depends="init,deps-jar,compile,-init-test-module-properties,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single">
         <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
         <j2seproject3:force-recompile destdir="${build.test.classes.dir}"/>
-        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}, module-info.java" modulepath="${javac.test.modulepath}" processorpath="${javac.test.processorpath}" sourcepath="${test.javafx.dir}:${test.console.dir}:${test.portrayal.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}" srcdir="${test.javafx.dir}:${test.console.dir}:${test.portrayal.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}">
+        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}, module-info.java" modulepath="${javac.test.modulepath}" processorpath="${javac.test.processorpath}" sourcepath="${test.javafx.dir}:${test.console.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}" srcdir="${test.javafx.dir}:${test.console.dir}:${test.earth-obs.dir}:${test.geotiff.dir}:${test.netcdf.dir}:${test.shapefile.dir}:${test.sql.dir}:${test.xmlstore.dir}:${test.storage.dir}:${test.feature.dir}:${test.referencing.dir}:${test.ref-by-id.dir}:${test.metadata.dir}:${test.utility.dir}:${test.fra-profile.dir}:${test.jpn-profile.dir}">
             <customize>
                 <compilerarg line="${javac.test.compilerargs}"/>
             </customize>
@@ -1930,7 +1921,6 @@
         <copy todir="${build.test.classes.dir}">
             <fileset dir="${test.javafx.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${test.console.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
-            <fileset dir="${test.portrayal.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${test.earth-obs.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${test.geotiff.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${test.netcdf.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
@@ -2050,7 +2040,7 @@
         <delete file="${built-clean.properties}" quiet="true"/>
     </target>
     <target if="already.built.clean.${basedir}" name="-warn-already-built-clean">
-        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.1 was already built"/>
+        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.0 was already built"/>
     </target>
     <target depends="init,-deps-clean-init" name="deps-clean" unless="no.deps">
         <mkdir dir="${build.dir}"/>
diff --git a/ide-project/NetBeans/nbproject/genfiles.properties b/ide-project/NetBeans/nbproject/genfiles.properties
index f00261f..9598b10 100644
--- a/ide-project/NetBeans/nbproject/genfiles.properties
+++ b/ide-project/NetBeans/nbproject/genfiles.properties
@@ -3,6 +3,6 @@
 build.xml.data.CRC32=58e6b21c
 build.xml.script.CRC32=462eaba0
 build.xml.stylesheet.CRC32=28e38971@1.53.1.46
-nbproject/build-impl.xml.data.CRC32=4e7b2ef2
-nbproject/build-impl.xml.script.CRC32=7ab9bfcf
+nbproject/build-impl.xml.data.CRC32=f188e30a
+nbproject/build-impl.xml.script.CRC32=933f5834
 nbproject/build-impl.xml.stylesheet.CRC32=12e0a6c2@1.101.0.48
diff --git a/ide-project/NetBeans/nbproject/project.properties b/ide-project/NetBeans/nbproject/project.properties
index 438b91d..468f1ef 100644
--- a/ide-project/NetBeans/nbproject/project.properties
+++ b/ide-project/NetBeans/nbproject/project.properties
@@ -73,7 +73,6 @@
 src.feature.dir      = ${project.root}/core/sis-feature/src/main/java
 test.feature.dir     = ${project.root}/core/sis-feature/src/test/java
 src.portrayal.dir    = ${project.root}/core/sis-portrayal/src/main/java
-test.portrayal.dir   = ${project.root}/core/sis-portrayal/src/test/java
 src.referencing.dir  = ${project.root}/core/sis-referencing/src/main/java
 test.referencing.dir = ${project.root}/core/sis-referencing/src/test/java
 src.ref-by-id.dir    = ${project.root}/core/sis-referencing-by-identifiers/src/main/java
@@ -92,7 +91,7 @@
 # Those dependencies must exist in the local Maven repository.
 # Those numbers should match the ones declared in the pom.xml files.
 #
-geoapi.version       = 3.1-SNAPSHOT
+geoapi.version       = 3.0.1
 jsr363.version       = 1.0
 jaxb.version         = 2.3.3
 jaxb.runtime         = 2.3.6
@@ -127,7 +126,7 @@
 maven.repository   = ${user.home}/.m2/repository
 endorsed.classpath =
 javac.classpath=\
-    ${maven.repository}/org/opengis/geoapi-pending/${geoapi.version}/geoapi-pending-${geoapi.version}.jar:\
+    ${maven.repository}/org/opengis/geoapi/${geoapi.version}/geoapi-${geoapi.version}.jar:\
     ${maven.repository}/javax/measure/unit-api/${jsr363.version}/unit-api-${jsr363.version}.jar:\
     ${maven.repository}/jakarta/xml/bind/jakarta.xml.bind-api/${jaxb.version}/jakarta.xml.bind-api-${jaxb.version}.jar:\
     ${maven.repository}/com/esri/geometry/esri-geometry-api/${esri.api.version}/esri-geometry-api-${esri.api.version}.jar:\
diff --git a/ide-project/NetBeans/nbproject/project.xml b/ide-project/NetBeans/nbproject/project.xml
index 69a414c..80deeba 100644
--- a/ide-project/NetBeans/nbproject/project.xml
+++ b/ide-project/NetBeans/nbproject/project.xml
@@ -21,7 +21,7 @@
     <type>org.netbeans.modules.java.j2seproject</type>
     <configuration>
         <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
-            <name>Apache SIS on GeoAPI 3.1</name>
+            <name>Apache SIS on GeoAPI 3.0</name>
             <source-roots>
                 <root id="src.local-src.dir" name="Local sources (unversioned)"/>
                 <root id="src.javafx.dir" name="JavaFX application"/>
@@ -46,7 +46,6 @@
             <test-roots>
                 <root id="test.javafx.dir" name="Test JavaFX application"/>
                 <root id="test.console.dir" name="Test Console"/>
-                <root id="test.portrayal.dir" name="Test Portrayal"/>
                 <root id="test.earth-obs.dir" name="Test Earth observation"/>
                 <root id="test.geotiff.dir" name="Test GeoTIFF"/>
                 <root id="test.netcdf.dir" name="Test NetCDF"/>
diff --git a/pom.xml b/pom.xml
index 39d7e26..0aa13ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -50,7 +50,7 @@
        ============================================================== -->
   <groupId>org.apache.sis</groupId>
   <artifactId>parent</artifactId>
-  <version>1.x-SNAPSHOT</version>
+  <version>1.3-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>Apache SIS</name>
@@ -424,7 +424,7 @@
       </dependency>
       <dependency>
         <groupId>org.opengis</groupId>
-        <artifactId>geoapi-pending</artifactId>
+        <artifactId>geoapi</artifactId>
         <version>${geoapi.version}</version>
       </dependency>
       <dependency>
@@ -558,7 +558,7 @@
     <sis.plugin.version>${project.version}</sis.plugin.version>
     <sis.non-free.version>1.2</sis.non-free.version>                <!-- Used only if "non-free" profile is activated. -->
     <javafx.version>18.0.1</javafx.version>                         <!-- Used only if "javafx" profile is activated. -->
-    <geoapi.version>3.1-SNAPSHOT</geoapi.version>
+    <geoapi.version>3.0.1</geoapi.version>
   </properties>
 
   <profiles>
@@ -875,7 +875,7 @@
           <links>
             <link>https://docs.oracle.com/javase/8/docs/api</link>
             <link>http://unitsofmeasurement.github.io/unit-api/site/apidocs</link>
-            <link>http://www.geoapi.org/snapshot/javadoc</link>
+            <link>http://www.geoapi.org/3.0/javadoc</link>
           </links>
 
           <additionalOptions>
@@ -994,17 +994,6 @@
     </pluginRepository>
   </pluginRepositories>
 
-  <!-- Used for GeoAPI snapshots only.
-       Shall be removed on SIS master. -->
-  <repositories>
-    <repository>
-      <id>geotoolkit</id>
-      <name>Geotoolkit.org repository</name>
-      <url>https://maven.geotoolkit.org</url>
-    </repository>
-  </repositories>
-
-
 
   <!-- ==============================================================
          Group of modules to build in approximate dependency order.
diff --git a/profiles/pom.xml b/profiles/pom.xml
index 799b04f..debee21 100644
--- a/profiles/pom.xml
+++ b/profiles/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
@@ -94,7 +94,7 @@
     </dependency>
     <dependency>
       <groupId>org.opengis</groupId>
-      <artifactId>geoapi-pending</artifactId>
+      <artifactId>geoapi</artifactId>
     </dependency>
 
     <!-- Test dependencies -->
diff --git a/profiles/sis-french-profile/pom.xml b/profiles/sis-french-profile/pom.xml
index 1c87162..f9d369c 100644
--- a/profiles/sis-french-profile/pom.xml
+++ b/profiles/sis-french-profile/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>profiles</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/profiles/sis-japan-profile/pom.xml b/profiles/sis-japan-profile/pom.xml
index af37c45..635ba9f 100644
--- a/profiles/sis-japan-profile/pom.xml
+++ b/profiles/sis-japan-profile/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>profiles</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/pom.xml b/storage/pom.xml
index b2b44ab..a64b2e5 100644
--- a/storage/pom.xml
+++ b/storage/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
@@ -136,7 +136,7 @@
     </dependency>
     <dependency>
       <groupId>org.opengis</groupId>
-      <artifactId>geoapi-pending</artifactId>
+      <artifactId>geoapi</artifactId>
     </dependency>
 
     <!-- Test dependencies -->
diff --git a/storage/sis-earth-observation/pom.xml b/storage/sis-earth-observation/pom.xml
index 551d818..71e7e10 100644
--- a/storage/sis-earth-observation/pom.xml
+++ b/storage/sis-earth-observation/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/Band.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/Band.java
index 52aa27f..936d84b 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/Band.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/Band.java
@@ -100,11 +100,7 @@
             sampleDimension = new DefaultSampleDimension();
         }
         sampleDimension.setDescription(band.title);
-        if (band.group.reflectance) {
-            sampleDimension.setUnits(Units.UNITY);
-        } else {
-            // W/(m² sr um)/DN
-        }
+        // Can not set units in GeoAPI 3.0 because the API is restricted to units of length.
     }
 
     /**
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java
index 411542f..54b612a 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java
@@ -858,7 +858,7 @@
      */
     final Metadata getMetadata() throws FactoryException {
         addLanguage(Locale.ENGLISH, MetadataBuilder.Scope.METADATA);
-        addResourceScope(ScopeCode.COVERAGE, null);
+        addResourceScope(ScopeCode.valueOf("COVERAGE"), null);
         addTopicCategory(TopicCategory.GEOSCIENTIFIC_INFORMATION);
         try {
             flushSceneTime();
diff --git a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
index fda6250..5a4040c 100644
--- a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
+++ b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
@@ -17,31 +17,10 @@
 package org.apache.sis.storage.landsat;
 
 import java.util.regex.Matcher;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import org.opengis.metadata.Metadata;
-import org.opengis.metadata.acquisition.Context;
-import org.opengis.metadata.acquisition.OperationType;
-import org.opengis.metadata.citation.Role;
-import org.opengis.metadata.citation.DateType;
-import org.opengis.metadata.content.CoverageContentType;
-import org.opengis.metadata.content.TransferFunctionType;
-import org.opengis.metadata.identification.Progress;
-import org.opengis.metadata.identification.TopicCategory;
-import org.opengis.metadata.extent.TemporalExtent;
-import org.opengis.metadata.maintenance.ScopeCode;
-import org.opengis.metadata.spatial.DimensionNameType;
-import org.opengis.util.FactoryException;
-import org.opengis.test.dataset.ContentVerifier;
-import org.apache.sis.storage.AbstractResource;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
-import static org.apache.sis.test.TestUtilities.date;
 
 
 /**
@@ -64,219 +43,4 @@
         assertTrue("matches", m.find());
         assertEquals("end", 22, m.end());
     }
-
-    /**
-     * Tests {@link MetadataReader#read(BufferedReader)}.
-     *
-     * <p><b>Note for maintainer:</b> if the result of this test changes, consider updating
-     * <a href="./doc-files/MetadataMapping.html">./doc-files/MetadataMapping.html</a> accordingly.</p>
-     *
-     * @throws IOException if an error occurred while reading the test file.
-     * @throws DataStoreException if a property value can not be parsed as a number or a date.
-     * @throws FactoryException if an error occurred while creating the Coordinate Reference System.
-     */
-    @Test
-    public void testRead() throws IOException, DataStoreException, FactoryException {
-        final Metadata actual;
-        try (BufferedReader in = new BufferedReader(new InputStreamReader(
-                MetadataReaderTest.class.getResourceAsStream("LandsatTest.txt"), "UTF-8")))
-        {
-            final MetadataReader reader = new MetadataReader(null, "LandsatTest.txt", createListeners());
-            reader.read(in);
-            actual = reader.getMetadata();
-        }
-        final ContentVerifier verifier = new ContentVerifier();
-        verifier.addPropertyToIgnore(Metadata.class, "metadataStandard");           // Because hard-coded in SIS.
-        verifier.addPropertyToIgnore(Metadata.class, "referenceSystemInfo");        // Very verbose and depends on EPSG connection.
-        verifier.addPropertyToIgnore(TemporalExtent.class, "extent");               // Because currently time-zone sensitive.
-        verifier.addMetadataToVerify(actual);
-        verifier.addExpectedValues(
-            "defaultLocale+otherLocale[0]",                                                          "en",
-            "metadataIdentifier.code",                                                               "LandsatTest",
-            "metadataScope[0].resourceScope",                                                        ScopeCode.COVERAGE,
-            "dateInfo[0].date",                                                                      date("2016-06-27 16:48:12"),
-            "dateInfo[0].dateType",                                                                  DateType.CREATION,
-            "identificationInfo[0].topicCategory[0]",                                                TopicCategory.GEOSCIENTIFIC_INFORMATION,
-            "identificationInfo[0].citation.date[0].date",                                           date("2016-06-27 16:48:12"),
-            "identificationInfo[0].citation.date[0].dateType",                                       DateType.CREATION,
-            "identificationInfo[0].citation.title",                                                  "LandsatTest",
-            "identificationInfo[0].credit[0]",                                                       "Derived from U.S. Geological Survey data",
-            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title",             "GeoTIFF Coverage Encoding Profile",
-            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]", "GeoTIFF",
-            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].party[0].name", "Open Geospatial Consortium",
-            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].role", Role.PRINCIPAL_INVESTIGATOR,
-            "identificationInfo[0].extent[0].geographicElement[0].extentTypeCode",                   true,
-            "identificationInfo[0].extent[0].geographicElement[0].westBoundLongitude",               108.34,
-            "identificationInfo[0].extent[0].geographicElement[0].eastBoundLongitude",               110.44,
-            "identificationInfo[0].extent[0].geographicElement[0].southBoundLatitude",                10.50,
-            "identificationInfo[0].extent[0].geographicElement[0].northBoundLatitude",                12.62,
-            "identificationInfo[0].spatialResolution[0].distance",                                    15.0,
-            "identificationInfo[0].spatialResolution[1].distance",                                    30.0,
-
-            "acquisitionInformation[0].platform[0].identifier.code",               "Pseudo LANDSAT",
-            "acquisitionInformation[0].platform[0].instrument[0].identifier.code", "Pseudo TIRS",
-            "acquisitionInformation[0].acquisitionRequirement[0].identifier.code", "Software unit tests",
-            "acquisitionInformation[0].operation[0].significantEvent[0].context",  Context.ACQUISITION,
-            "acquisitionInformation[0].operation[0].significantEvent[0].time",     date("2016-06-26 03:02:01.090"),
-            "acquisitionInformation[0].operation[0].status",                       Progress.COMPLETED,
-            "acquisitionInformation[0].operation[0].type",                         OperationType.REAL,
-
-            "contentInfo[0].processingLevelCode.authority.title",          "Landsat",
-            "contentInfo[0].processingLevelCode.codeSpace",                "Landsat",
-            "contentInfo[0].processingLevelCode.code",                     "Pseudo LT1",
-
-            "contentInfo[0].attributeGroup[0].attribute[0].description",   "Coastal Aerosol",
-            "contentInfo[0].attributeGroup[0].attribute[1].description",   "Blue",
-            "contentInfo[0].attributeGroup[0].attribute[2].description",   "Green",
-            "contentInfo[0].attributeGroup[0].attribute[3].description",   "Red",
-            "contentInfo[0].attributeGroup[0].attribute[4].description",   "Near-Infrared",
-            "contentInfo[0].attributeGroup[0].attribute[5].description",   "Short Wavelength Infrared (SWIR) 1",
-            "contentInfo[0].attributeGroup[0].attribute[6].description",   "Short Wavelength Infrared (SWIR) 2",
-            "contentInfo[0].attributeGroup[0].attribute[7].description",   "Cirrus",
-            "contentInfo[0].attributeGroup[1].attribute[0].description",   "Panchromatic",
-            "contentInfo[0].attributeGroup[2].attribute[0].description",   "Thermal Infrared Sensor (TIRS) 1",
-            "contentInfo[0].attributeGroup[2].attribute[1].description",   "Thermal Infrared Sensor (TIRS) 2",
-
-            "contentInfo[0].attributeGroup[0].attribute[0].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[1].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[2].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[3].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[4].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[5].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[6].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[7].minValue",      1.0,
-            "contentInfo[0].attributeGroup[1].attribute[0].minValue",      1.0,
-            "contentInfo[0].attributeGroup[2].attribute[0].minValue",      1.0,
-            "contentInfo[0].attributeGroup[2].attribute[1].minValue",      1.0,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[1].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[2].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[3].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[4].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[5].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[6].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[7].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[1].attribute[0].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[2].attribute[0].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[2].attribute[1].maxValue",      65535.0,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].peakResponse",    433.0,
-            "contentInfo[0].attributeGroup[0].attribute[1].peakResponse",    482.0,
-            "contentInfo[0].attributeGroup[0].attribute[2].peakResponse",    562.0,
-            "contentInfo[0].attributeGroup[0].attribute[3].peakResponse",    655.0,
-            "contentInfo[0].attributeGroup[0].attribute[4].peakResponse",    865.0,
-            "contentInfo[0].attributeGroup[0].attribute[5].peakResponse",   1610.0,
-            "contentInfo[0].attributeGroup[0].attribute[6].peakResponse",   2200.0,
-            "contentInfo[0].attributeGroup[0].attribute[7].peakResponse",   1375.0,
-            "contentInfo[0].attributeGroup[1].attribute[0].peakResponse",    590.0,
-            "contentInfo[0].attributeGroup[2].attribute[0].peakResponse",  10800.0,
-            "contentInfo[0].attributeGroup[2].attribute[1].peakResponse",  12000.0,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[1].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[2].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[3].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[4].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[5].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[6].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[7].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[1].attribute[0].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[2].attribute[0].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[2].attribute[1].transferFunctionType",  TransferFunctionType.LINEAR,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[1].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[2].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[3].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[4].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[5].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[6].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[0].attribute[7].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[1].attribute[0].scaleFactor",  2.0E-5,
-            "contentInfo[0].attributeGroup[2].attribute[0].scaleFactor",  0.000334,
-            "contentInfo[0].attributeGroup[2].attribute[1].scaleFactor",  0.000334,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[1].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[2].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[3].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[4].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[5].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[6].offset",      -0.1,
-            "contentInfo[0].attributeGroup[0].attribute[7].offset",      -0.1,
-            "contentInfo[0].attributeGroup[1].attribute[0].offset",      -0.1,
-            "contentInfo[0].attributeGroup[2].attribute[0].offset",       0.1,
-            "contentInfo[0].attributeGroup[2].attribute[1].offset",       0.1,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[1].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[2].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[3].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[4].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[5].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[6].units", "",
-            "contentInfo[0].attributeGroup[0].attribute[7].units", "",
-            "contentInfo[0].attributeGroup[1].attribute[0].units", "",
-
-            "contentInfo[0].attributeGroup[0].attribute[0].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[1].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[2].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[3].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[4].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[5].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[6].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[7].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[1].attribute[0].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[2].attribute[0].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[2].attribute[1].boundUnits",   "nm",
-
-            "contentInfo[0].attributeGroup[0].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT,
-            "contentInfo[0].attributeGroup[1].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT,
-            "contentInfo[0].attributeGroup[2].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT,
-
-            "contentInfo[0].cloudCoverPercentage",         8.3,
-            "contentInfo[0].illuminationAzimuthAngle",   116.9,
-            "contentInfo[0].illuminationElevationAngle",  58.8,
-
-            "spatialRepresentationInfo[0].numberOfDimensions",                       2,
-            "spatialRepresentationInfo[1].numberOfDimensions",                       2,
-            "spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionName", DimensionNameType.SAMPLE,
-            "spatialRepresentationInfo[1].axisDimensionProperties[0].dimensionName", DimensionNameType.SAMPLE,
-            "spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionName", DimensionNameType.LINE,
-            "spatialRepresentationInfo[1].axisDimensionProperties[1].dimensionName", DimensionNameType.LINE,
-            "spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionSize", 7600,
-            "spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionSize", 7800,
-            "spatialRepresentationInfo[1].axisDimensionProperties[0].dimensionSize", 15000,
-            "spatialRepresentationInfo[1].axisDimensionProperties[1].dimensionSize", 15500,
-            "spatialRepresentationInfo[0].transformationParameterAvailability",      false,
-            "spatialRepresentationInfo[1].transformationParameterAvailability",      false,
-            "spatialRepresentationInfo[0].checkPointAvailability",                   false,
-            "spatialRepresentationInfo[1].checkPointAvailability",                   false,
-
-            "resourceLineage[0].source[0].description", "Pseudo GLS");
-
-        verifier.assertMetadataEquals();
-    }
-
-    /**
-     * Creates a dummy set of store listeners.
-     * Used only for constructors that require a non-null {@link StoreListeners} instance.
-     *
-     * @return a dummy set of listeners.
-     */
-    private static StoreListeners createListeners() {
-        final class DummyResource extends AbstractResource {
-            /** Creates a dummy resource without parent. */
-            DummyResource() {
-                super(null, false);
-            }
-
-            /** Makes listeners accessible to this package. */
-            StoreListeners listeners() {
-                return listeners;
-            }
-        }
-        return new DummyResource().listeners();
-    }
 }
diff --git a/storage/sis-geotiff/pom.xml b/storage/sis-geotiff/pom.xml
index 7a04cee..1754cc4 100644
--- a/storage/sis-geotiff/pom.xml
+++ b/storage/sis-geotiff/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
index 6995585..7d684e8 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -294,7 +294,7 @@
             listeners.warning(e);
         }
         builder.addEncoding(encoding, MetadataBuilder.Scope.METADATA);
-        builder.addResourceScope(ScopeCode.COVERAGE, null);
+        builder.addResourceScope(ScopeCode.valueOf("COVERAGE"), null);
     }
 
     /**
diff --git a/storage/sis-netcdf/pom.xml b/storage/sis-netcdf/pom.xml
index 8f79efc..399f87e 100644
--- a/storage/sis-netcdf/pom.xml
+++ b/storage/sis-netcdf/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
@@ -121,7 +121,6 @@
       <optional>true</optional>
     </dependency>
 
-    <!-- Leverage GeoAPI tests. -->
     <dependency>
       <groupId>org.apache.sis.core</groupId>
       <artifactId>sis-referencing</artifactId>
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
index 196ae9f..d266ede 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
@@ -583,7 +583,7 @@
          */
         final String alt = coordinates.getAttributeAsString(CDM.LONG_NAME);
         if (alt != null && !similar(alt, name)) {
-            properties.put(org.opengis.metadata.Identifier.DESCRIPTION_KEY, alt);   // Description associated to primary name.
+            properties.put(org.apache.sis.referencing.ImmutableIdentifier.DESCRIPTION_KEY, alt);   // Description associated to primary name.
             if (!similar(alt, standardName)) {
                 aliases.add(new NamedIdentifier(null, alt));                        // Additional alias.
             }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
index 5e73914..c162ec9 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
@@ -61,6 +61,9 @@
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+
 
 /**
  * Temporary object for building a coordinate reference system from the variables in a netCDF file.
@@ -786,7 +789,8 @@
          */
         private static final Conversion UNKNOWN_PROJECTION;
         static {
-            final CoordinateOperationFactory factory = DefaultFactories.forBuildin(CoordinateOperationFactory.class);
+            final DefaultCoordinateOperationFactory factory = DefaultFactories.forBuildin(
+                    CoordinateOperationFactory.class, DefaultCoordinateOperationFactory.class);
             try {
                 final OperationMethod method = factory.getOperationMethod(Equirectangular.NAME);
                 UNKNOWN_PROJECTION = factory.createDefiningConversion(
@@ -1032,7 +1036,7 @@
             try {
                 switch (axes.length) {
                     case 0:  break;     // Should never happen but we are paranoiac.
-                    case 1:  coordinateSystem = factory.createParametricCS(properties, axes[0]); return;
+                    case 1:  coordinateSystem = new org.apache.sis.referencing.cs.DefaultParametricCS(properties, axes[0]); return;
                     case 2:  coordinateSystem = factory.createAffineCS(properties, axes[0], axes[1]); return;
                     default: coordinateSystem = factory.createAffineCS(properties, axes[0], axes[1], axes[2]); return;
                 }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
index cbca54e..79d2359 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
@@ -49,6 +49,9 @@
 import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
 import ucar.nc2.constants.CF;
 
+// Branch-dependent imports
+import org.apache.sis.util.iso.DefaultNameFactory;
+
 
 /**
  * The API used internally by Apache SIS for fetching variables and attribute values from a netCDF file.
@@ -98,7 +101,7 @@
     /**
      * The factory to use for creating variable identifiers.
      */
-    public final NameFactory nameFactory;
+    public final DefaultNameFactory nameFactory;
 
     /**
      * The library for geometric objects, or {@code null} for the default.
@@ -165,7 +168,7 @@
         Objects.requireNonNull(listeners);
         this.geomlib      = geomlib;
         this.listeners    = listeners;
-        this.nameFactory  = DefaultFactories.forBuildin(NameFactory.class);
+        this.nameFactory  = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
         this.datumCache   = new Datum[CRSBuilder.DATUM_CACHE_SIZE];
         this.gridMapping  = new HashMap<>();
         localizationGrids = new HashMap<>();
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/FeatureSet.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/FeatureSet.java
index e12de7c..761386b 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/FeatureSet.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/FeatureSet.java
@@ -50,9 +50,9 @@
 import ucar.nc2.constants.CF;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Attribute;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractAttribute;
 
 
 /**
@@ -87,7 +87,7 @@
 
     /**
      * The number of instances for each feature, or {@code null} if none. If non-null, then the number of features
-     * is the length of this vector and each {@link Feature} instance has multi-valued properties with a number of
+     * is the length of this vector and each {@code Feature} instance has multi-valued properties with a number of
      * elements given by this count. If null, the number of features is determined by the length of other variables.
      *
      * @see #getFeatureCount()
@@ -168,7 +168,7 @@
     /**
      * The type of all features to be read by this {@code FeatureSet}.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType type;
 
     /**
      * Creates a new discrete sampling parser for features identified by the given variable.
@@ -579,7 +579,7 @@
      * Returns the type of all features to be read by this {@code FeatureSet}.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -638,7 +638,7 @@
      * @param  parallel  ignored, since current version does not support parallelism.
      */
     @Override
-    public Stream<Feature> features(boolean parallel) throws DataStoreException {
+    public Stream<AbstractFeature> features(boolean parallel) throws DataStoreException {
         try {
             return StreamSupport.stream(new Iter(), false);
         } catch (IOException e) {
@@ -649,7 +649,7 @@
     /**
      * Implementation of the iterator returned by {@link #features(boolean)}.
      */
-    private final class Iter implements Spliterator<Feature> {
+    private final class Iter implements Spliterator<AbstractFeature> {
         /**
          * Expected number of feature instances.
          */
@@ -742,8 +742,8 @@
          * @throws BackingStoreException if an {@link IOException} or {@link DataStoreException} occurred.
          */
         @Override
-        public boolean tryAdvance(final Consumer<? super Feature> action) {
-            final Feature feature = type.newInstance();
+        public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
+            final AbstractFeature feature = type.newInstance();
             final Vector[] coordinateValues;
             int offset, length;
             try {
@@ -867,7 +867,7 @@
              * The time vector is the first vector after the geometry dimensions.
              */
             if (hasTime) {
-                MovingFeatures.setTimes((Attribute<?>) feature.getProperty(TRAJECTORY),
+                MovingFeatures.setTimes((AbstractAttribute<?>) feature.getProperty(TRAJECTORY),
                                         coordinateValues[geometryDimension], timeCRS);
             }
             action.accept(feature);
@@ -926,7 +926,7 @@
          * Current implementation can not split this iterator.
          */
         @Override
-        public Spliterator<Feature> trySplit() {
+        public Spliterator<AbstractFeature> trySplit() {
             return null;
         }
 
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
index aedeccc..c01d491 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
@@ -41,7 +41,6 @@
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.referencing.operation.CoordinateOperationFactory;
 import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.Conversion;
@@ -77,6 +76,9 @@
 import org.apache.sis.measure.Units;
 import ucar.nc2.constants.CF;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+
 
 /**
  * Temporary objects for creating a {@link GridGeometry} instance defined by attributes on a variable.
@@ -217,7 +219,7 @@
              * than OGC names, but Apache SIS implementations of map projections know how to handle them, including
              * the redundant parameters like "inverse_flattening" and "earth_radius".
              */
-            final CoordinateOperationFactory opFactory = node.decoder.getCoordinateOperationFactory();
+            final DefaultCoordinateOperationFactory opFactory = node.decoder.getCoordinateOperationFactory();
             final OperationMethod method = opFactory.getOperationMethod((String) definition.remove(CF.GRID_MAPPING_NAME));
             final ParameterValueGroup parameters = method.getParameters().createValue();
             for (final Iterator<Map.Entry<String,Object>> it = definition.entrySet().iterator(); it.hasNext();) {
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 1535c63..c3a86a6 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
@@ -24,8 +24,8 @@
 import ucar.nc2.ft.DsgFeatureCollection;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -62,7 +62,7 @@
     }
 
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         throw new UnsupportedOperationException();      // TODO
     }
 
@@ -70,7 +70,7 @@
      * Returns the stream of features.
      */
     @Override
-    public Stream<Feature> features(boolean parallel) {
+    public Stream<AbstractFeature> features(boolean parallel) {
         throw new UnsupportedOperationException();      // TODO
     }
 }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
index 1c6591a..189bbf3 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
@@ -351,7 +351,7 @@
      * This is a character array with a line for each invocation of a program that has modified the dataset.
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getResourceLineages() resourceLineage} /
+     * {@link org.apache.sis.metadata.iso.DefaultMetadata#getResourceLineages() resourceLineage} /
      * {@link Lineage#getStatement() statement}</li></ul>
      *
      * <div class="note"><b>Note:</b>
@@ -368,7 +368,7 @@
      * The {@value} attribute name for the method of production of the original data (<em>Recommended</em>).
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getResourceLineages() resourceLineage} /
+     * {@link org.apache.sis.metadata.iso.DefaultMetadata#getResourceLineages() resourceLineage} /
      * {@link Lineage#getSources() source} /
      * {@link Source#getDescription() description}</li></ul>
      *
@@ -402,7 +402,7 @@
      * subgroup.
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getDateInfo() dateInfo}
+     * {@code dateInfo}
      * {@link CitationDate#getDate() date} with {@link DateType#CREATION}</li></ul>
      */
     public static final String METADATA_CREATION = "metadata_creation";
@@ -412,7 +412,7 @@
      * (<em>Suggested</em>).
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getDateInfo() dateInfo}
+     * {@code dateInfo}
      * {@link CitationDate#getDate() date} with {@link DateType#REVISION}</li></ul>
      *
      * @since 0.8
@@ -1161,7 +1161,7 @@
      * For example it may be the URL to an ISO 19115 metadata in XML format.
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getMetadataLinkages() metadataLinkage} /
+     * {@code metadataLinkage} /
      * {@link OnlineResource#getLinkage() linkage}</li></ul>
      *
      * @see <a href="http://wiki.esipfed.org/index.php/Attribute_Convention_for_Data_Discovery#metadata_link">ESIP reference</a>
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
index d797390..031866e 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
@@ -35,7 +35,6 @@
 import javax.measure.format.ParserException;
 
 import org.opengis.util.CodeList;
-import org.opengis.util.NameFactory;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.Identifier;
@@ -50,6 +49,7 @@
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 import org.apache.sis.util.iso.Types;
+import org.apache.sis.util.iso.DefaultNameFactory;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.metadata.iso.DefaultMetadata;
@@ -82,7 +82,6 @@
 
 import static java.util.Collections.singleton;
 import static org.apache.sis.storage.netcdf.AttributeNames.*;
-import static org.apache.sis.internal.util.CollectionsExt.first;
 
 
 /**
@@ -471,16 +470,15 @@
          * If we can not share the whole existing instance, we usually can share parts of it like the address.
          */
         ResponsibleParty responsibility = pointOfContact;
-        Contact        contact        = null;
-        Address        address        = null;
-        OnlineResource resource       = null;
+        Contact          contact        = null;
+        Address          address        = null;
+        OnlineResource   resource       = null;
         if (responsibility != null) {
-            final Party party = first(responsibility.getParties());
-            if (party != null) {
-                contact = first(party.getContactInfo());
+            { // Additional indentation for having the same level than SIS branches for GeoAPI snapshots (makes merges easier).
+                contact = responsibility.getContactInfo();
                 if (contact != null) {
-                    address  = first(contact.getAddresses());
-                    resource = first(contact.getOnlineResources());
+                    address  = contact.getAddress();
+                    resource = contact.getOnlineResource();
                 }
                 if (!canShare(resource, url)) {
                     resource       = null;
@@ -493,14 +491,9 @@
                     responsibility = null;
                 }
                 if (responsibility != null) {
-                    if (party instanceof Organisation) {
-                        // Individual (if any) is considered an organisation member. See comment in next block.
-                        if (!canShare(party.getName(), organisationName) ||
-                            !canShare(first(((Organisation) party).getIndividual()).getName(), individualName))
-                        {
-                            responsibility = null;
-                        }
-                    } else if (!canShare(party.getName(), individualName)) {
+                    if (!canShare(responsibility.getOrganisationName(), organisationName) ||
+                        !canShare(responsibility.getIndividualName(),   individualName))
+                    {
                         responsibility = null;
                     }
                 }
@@ -520,7 +513,7 @@
             if (individualName != null || organisationName != null || contact != null) {        // Do not test role.
                 AbstractParty party = null;
                 if (individualName   != null) party = new DefaultIndividual(individualName, null, null);
-                if (organisationName != null) party = new DefaultOrganisation(organisationName, null, (Individual) party, null);
+                if (organisationName != null) party = new DefaultOrganisation(organisationName, null, (DefaultIndividual) party, null);
                 if (party            == null) party = isOrganisation(keys) ? new DefaultOrganisation() : new DefaultIndividual();
                 if (contact          != null) party.setContactInfo(singleton(contact));
                 responsibility = new DefaultResponsibleParty(role);
@@ -601,9 +594,9 @@
                 addCitedResponsibleParty(contributor, null);
             }
             final ResponsibleParty r = createResponsibleParty(PUBLISHER, false);
-            if (r != null) {
+            if (r instanceof DefaultResponsibility) {
                 addDistributor(r);
-                for (final Party party : r.getParties()) {
+                for (final AbstractParty party : ((DefaultResponsibility) r).getParties()) {
                     publisher = addIfNonNull(publisher, party.getName());
                 }
             }
@@ -653,8 +646,8 @@
         addUseLimitation          (stringValue(LICENSE));
         addKeywords(standard,  KeywordType.THEME,       stringValue(STANDARD_NAME.VOCABULARY));
         addKeywords(keywords,  KeywordType.THEME,       stringValue(KEYWORDS.VOCABULARY));
-        addKeywords(project,   KeywordType.PROJECT,     null);
-        addKeywords(publisher, KeywordType.DATA_CENTRE, null);
+        addKeywords(project,   KeywordType.valueOf("PROJECT"), null);
+        addKeywords(publisher, KeywordType.valueOf("DATA_CENTRE"), null);
         /*
          * Add geospatial bounds as a geometric object. This optional operation requires
          * an external library (ESRI or JTS) to be present on the classpath.
@@ -951,7 +944,7 @@
         newSampleDimension();
         final String name = Strings.trimOrNull(variable.getName());
         if (name != null) {
-            final NameFactory f = decoder.nameFactory;
+            final DefaultNameFactory f = decoder.nameFactory;
             final StringBuilder buffer = new StringBuilder(20);
             variable.writeDataTypeName(buffer);
             setBandIdentifier(f.createMemberName(null, name, f.createTypeName(null, buffer.toString())));
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java
index d55df4f..6a0ebf8 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java
@@ -19,7 +19,6 @@
 import java.util.Date;
 import java.io.IOException;
 import org.apache.sis.storage.DataStoreException;
-import org.opengis.test.dataset.TestData;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/FeatureSetTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/FeatureSetTest.java
deleted file mode 100644
index 95e9eca..0000000
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/FeatureSetTest.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * 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.netcdf;
-
-import java.awt.Shape;
-import java.awt.geom.PathIterator;
-import java.util.Iterator;
-import java.util.Collection;
-import java.io.IOException;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Optional;
-import org.opengis.referencing.crs.GeographicCRS;
-import org.apache.sis.internal.feature.AttributeConvention;
-import org.apache.sis.storage.DataStore;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.test.DependsOn;
-import org.junit.Test;
-
-import static org.apache.sis.test.Assert.*;
-
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.metadata.Metadata;
-import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.test.dataset.TestData;
-
-
-/**
- * Tests the {@link FeatureSet} implementation. The default implementation uses the UCAR library,
- * which is is our reference implementation. Subclass overrides {@link #createDecoder(TestData)}
- * method in order to test a different implementation.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-@DependsOn(VariableTest.class)
-public strictfp class FeatureSetTest extends TestCase {
-    /**
-     * Type of the features read from the netCDF file.
-     */
-    private FeatureType type;
-
-    /**
-     * Index of the feature to verify.
-     */
-    private int featureIndex;
-
-    /**
-     * Instant from which time are measured.
-     */
-    private final Instant timeOrigin;
-
-    /**
-     * Creates a new test case.
-     */
-    public FeatureSetTest() {
-        timeOrigin = Instant.parse("2014-11-29T00:00:00Z");
-    }
-
-    /**
-     * Returns a dummy data store implementation for the sole purpose of providing a non-null lock.
-     */
-    private static DataStore lock() {
-        return new DataStore() {
-            @Override public Optional<ParameterValueGroup> getOpenParameters() {return Optional.empty();}
-            @Override public Metadata getMetadata() {return null;}
-            @Override public void close() {}
-        };
-    }
-
-    /**
-     * Tests {@link FeatureSet} with a moving features file.
-     *
-     * @throws IOException if an I/O error occurred while opening the file.
-     * @throws DataStoreException if a logical error occurred.
-     */
-    @Test
-    public void testMovingFeatures() throws IOException, DataStoreException {
-        final DataStore lock = lock();
-        final FeatureSet[] features;
-        synchronized (lock) {
-            features = FeatureSet.create(selectDataset(TestData.MOVING_FEATURES), lock);
-        }
-        assertEquals(1, features.length);
-        type = features[0].getType();
-        verifyType(type.getProperties(false).iterator());
-        features[0].features(false).forEach(this::verifyInstance);
-    }
-
-    /**
-     * Verifies that given properties are the expected ones
-     * for {@link TestData#MOVING_FEATURES} feature type.
-     */
-    private static void verifyType(final Iterator<? extends PropertyType> it) {
-        assertEquals("sis:identifier", it.next().getName().toString());
-        assertEquals("sis:envelope",   it.next().getName().toString());
-        assertEquals("sis:geometry",   it.next().getName().toString());
-
-        AttributeType<?> at = (AttributeType<?>) it.next();
-        assertEquals("features", at.getName().toString());
-
-        at = (AttributeType<?>) it.next();
-        assertEquals("trajectory", at.getName().toString());
-        assertEquals(Shape.class, at.getValueClass());
-
-        at = (AttributeType<?>) it.next();
-        assertEquals("stations", at.getName().toString());
-        assertEquals(String.class, at.getValueClass());
-
-        assertFalse(it.hasNext());
-    }
-
-    /**
-     * Verifies the given feature instance.
-     */
-    private void verifyInstance(final Feature instance) {
-        assertSame(type, instance.getType());
-        final float[] longitudes, latitudes;
-        final short[] times;                    // In minutes since 2014-11-29 00:00:00.
-        final String[] stations;
-        final String identifier;
-        switch (featureIndex++) {
-            case 0: {
-                identifier = "a4078a16";
-                longitudes = new float[] {139.622715f, 139.696899f, 139.740440f, 139.759640f, 139.763328f, 139.766084f};
-                latitudes  = new float[] { 35.466188f,  35.531328f,  35.630152f,  35.665498f,  35.675069f,  35.681382f};
-                times      = new short[] {       1068,        1077,        1087,        1094,        1096,        1098};
-                stations   = new String[] {
-                    "Yokohama", "Kawasaki", "Shinagawa", "Shinbashi", "Yurakucho", "Tokyo"
-                };
-                break;
-            }
-            case 1: {
-                identifier = "1e146c16";
-                longitudes = new float[] {139.700258f, 139.730667f, 139.763786f, 139.774219f};
-                latitudes  = new float[] { 35.690921f,  35.686014f,  35.699855f,  35.698683f};
-                times      = new short[] {       1075,        1079,        1087,        1090};
-                stations   = new String[] {
-                    "Shinjuku", "Yotsuya", "Ochanomizu", "Akihabara"
-                };
-                break;
-            }
-            case 2: {
-                identifier = "f50ff004";
-                longitudes = new float[] {139.649867f, 139.665652f, 139.700258f};
-                latitudes  = new float[] { 35.705385f,  35.706032f,  35.690921f};
-                times      = new short[] {       3480,        3482,        3486};
-                stations   = new String[] {
-                    "Koenji", "Nakano", "Shinjuku"
-                };
-                break;
-            }
-            default: {
-                fail("Unexpected feature instance.");
-                return;
-            }
-        }
-        // Convert the time vector to an array of instants.
-        final Instant[] instants = new Instant[times.length];
-        for (int i=0; i<times.length; i++) {
-            instants[i] = timeOrigin.plus(times[i], ChronoUnit.MINUTES);
-        }
-        /*
-         * Verify property values and characteristics.
-         */
-        assertEquals("identifier", identifier, instance.getPropertyValue("features"));
-        final Attribute<?> trajectory = (Attribute<?>) instance.getProperty("trajectory");
-        asserLineStringEquals((Shape) trajectory.getValue(), longitudes, latitudes);
-        assertArrayEquals("stations", stations, ((Collection<?>) instance.getPropertyValue("stations")).toArray());
-        assertArrayEquals("times", instants, trajectory.characteristics().get("datetimes").getValues().toArray());
-        assertInstanceOf("CRS", GeographicCRS.class, AttributeConvention.getCRSCharacteristic(trajectory));
-    }
-
-    /**
-     * Asserts the the given shape is a line string with the following coordinates.
-     *
-     * @param  trajectory  the shape to verify.
-     * @param  x           expected X coordinates.
-     * @param  y           expected Y coordinates.
-     */
-    private static void asserLineStringEquals(final Shape trajectory, final float[] x, final float[] y) {
-        assertEquals(x.length, y.length);
-        final PathIterator it = trajectory.getPathIterator(null);
-        final float[] point = new float[2];
-        for (int i=0; i < x.length; i++) {
-            assertFalse(it.isDone());
-            assertEquals(i == 0 ? PathIterator.SEG_MOVETO : PathIterator.SEG_LINETO, it.currentSegment(point));
-            assertEquals("x", x[i], point[0], STRICT);
-            assertEquals("y", y[i], point[1], STRICT);
-            it.next();
-        }
-        assertTrue(it.isDone());
-    }
-}
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
index 7f14447..286a8e5 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
@@ -22,7 +22,6 @@
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
-import org.opengis.test.dataset.TestData;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
index f6d8c9f..43c0374 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
@@ -27,7 +27,6 @@
 import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.storage.event.StoreListeners;
-import org.opengis.test.dataset.TestData;
 import ucar.nc2.dataset.NetcdfDataset;
 import ucar.nc2.NetcdfFile;
 import org.junit.AfterClass;
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestData.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestData.java
new file mode 100644
index 0000000..0f6f435
--- /dev/null
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestData.java
@@ -0,0 +1,48 @@
+/*
+ * 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.netcdf;
+
+import java.net.URL;
+import java.io.InputStream;
+
+import static org.junit.Assume.*;
+
+
+/**
+ * Place-holder for a GeoAPI 3.1 class. Used only for allowing the code to compile.
+ * For real test execution, see the development branches on GeoAPI 4.0-SNAPSHOT.
+ */
+public enum TestData {
+    NETCDF_2D_GEOGRAPHIC,
+
+    NETCDF_4D_PROJECTED;
+
+    public URL location() {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+        return null;
+    }
+
+    public InputStream open() {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+        return null;
+    }
+
+    public byte[] content() {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+        return null;
+    }
+}
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
index d05cf6b..099f587 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
@@ -26,7 +26,6 @@
 import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
 import org.apache.sis.measure.Units;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.dataset.TestData;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
@@ -309,18 +308,4 @@
             assertEquals("Longitude value", -180 + 5*i, data.floatValue(i), 0f);
         }
     }
-
-    /**
-     * Tests {@link Variable#readAnyType()} on strings.
-     *
-     * @throws IOException if an error occurred while reading the netCDF file.
-     * @throws DataStoreException if a logical error occurred.
-     */
-    @Test
-    public void testReadStrings() throws IOException, DataStoreException {
-        final Variable variable = selectDataset(TestData.MOVING_FEATURES).findVariable("features");
-        assertEquals("features", variable.getName());
-        final List<?> identifiers = variable.readAnyType();
-        assertArrayEquals(new String[] {"a4078a16", "1e146c16", "f50ff004", "", ""}, identifiers.toArray());
-    }
 }
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java
index d426ecd..19c45b4 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java
@@ -20,13 +20,13 @@
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
+import org.apache.sis.internal.netcdf.TestData;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.DecoderTest;
 import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.dataset.TestData;
 
 
 /**
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/FeatureSetTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/FeatureSetTest.java
deleted file mode 100644
index a9c35b5..0000000
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/FeatureSetTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.netcdf.impl;
-
-import java.io.IOException;
-import org.opengis.test.dataset.TestData;
-import org.apache.sis.internal.netcdf.Decoder;
-import org.apache.sis.storage.DataStoreException;
-
-
-/**
- * Tests the {@link org.apache.sis.internal.netcdf.FeatureSet} implementation
- * using the Apache SIS implementation of netCDF reader.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class FeatureSetTest extends org.apache.sis.internal.netcdf.FeatureSetTest {
-    /**
-     * Creates a new decoder for the specified dataset.
-     *
-     * @param  file  the dataset as one of the above-cited constants.
-     * @return the decoder for the specified dataset.
-     * @throws IOException if an I/O error occurred while opening the file.
-     * @throws DataStoreException if a logical error occurred.
-     */
-    @Override
-    protected Decoder createDecoder(final TestData file) throws IOException, DataStoreException {
-        return ChannelDecoderTest.createChannelDecoder(file);
-    }
-}
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java
index e084c7b..dbc4087 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java
@@ -25,7 +25,7 @@
 import org.apache.sis.test.DependsOn;
 
 // Branch-specific imports
-import org.opengis.test.dataset.TestData;
+import org.apache.sis.internal.netcdf.TestData;
 
 
 /**
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java
index 759ea96..849c221 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java
@@ -19,9 +19,9 @@
 import java.io.IOException;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.VariableTest;
+import org.apache.sis.internal.netcdf.TestData;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.dataset.TestData;
 
 
 /**
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
index 5ad546a..4c391e7 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
@@ -27,12 +27,12 @@
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.metadata.spatial.CellGeometry;
 import org.opengis.metadata.maintenance.ScopeCode;
-import org.opengis.test.dataset.ContentVerifier;
-import org.opengis.test.dataset.TestData;
+import org.apache.sis.internal.netcdf.TestData;
 import org.apache.sis.internal.netcdf.TestCase;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.impl.ChannelDecoderTest;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.test.ContentVerifier;
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
@@ -76,7 +76,7 @@
         try (Decoder input = ChannelDecoderTest.createChannelDecoder(TestData.NETCDF_2D_GEOGRAPHIC)) {
             metadata = new MetadataReader(input).read();
         }
-        compareToExpected(metadata).assertMetadataEquals();
+        compareToExpected(metadata);
     }
 
     /**
@@ -92,22 +92,21 @@
         try (Decoder input = createDecoder(TestData.NETCDF_2D_GEOGRAPHIC)) {
             metadata = new MetadataReader(input).read();
         }
-        final ContentVerifier verifier = compareToExpected(metadata);
-        verifier.addExpectedValue("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[1]", "NetCDF-3/CDM");
-        verifier.assertMetadataEquals();
+        compareToExpected(metadata);
     }
 
     /**
-     * Creates comparator for the string representation of the given metadata object with the expected one.
+     * Compares the string representation of the given metadata object with the expected one.
      * The given metadata shall have been created from the {@link TestData#NETCDF_2D_GEOGRAPHIC} dataset.
      */
-    static ContentVerifier compareToExpected(final Metadata actual) {
+    static void compareToExpected(final Metadata actual) {
         final ContentVerifier verifier = new ContentVerifier();
         verifier.addPropertyToIgnore(Metadata.class, "metadataStandard");
         verifier.addPropertyToIgnore(Metadata.class, "referenceSystemInfo");
+        verifier.addPropertyToIgnore(Metadata.class, "alternateTitle");
         verifier.addPropertyToIgnore(TemporalExtent.class, "extent");
         verifier.addMetadataToVerify(actual);
-        verifier.addExpectedValues(
+        verifier.assertMetadataEquals(
             // Hard-coded
             "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]", "NetCDF",
             "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title", "NetCDF Classic and 64-bit Offset Format",
@@ -162,7 +161,5 @@
             "contentInfo[0].attributeGroup[0].attribute[0].units",                     "°C",
 
             "resourceLineage[0].statement", "Decimated and modified by GeoAPI for inclusion in conformance test suite.");
-
-        return verifier;
     }
 }
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
index ac33a21..5b9b121 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
@@ -28,7 +28,7 @@
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.Version;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.dataset.TestData;
+import org.apache.sis.internal.netcdf.TestData;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreTest.java
index e64cb7e..a82538a 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreTest.java
@@ -22,7 +22,7 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.util.Version;
-import org.opengis.test.dataset.TestData;
+import org.apache.sis.internal.netcdf.TestData;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
@@ -63,7 +63,7 @@
             metadata = store.getMetadata();
             assertSame("Should be cached.", metadata, store.getMetadata());
         }
-        MetadataReaderTest.compareToExpected(metadata).assertMetadataEquals();
+        MetadataReaderTest.compareToExpected(metadata);
     }
 
     /**
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/test/suite/NetcdfTestSuite.java b/storage/sis-netcdf/src/test/java/org/apache/sis/test/suite/NetcdfTestSuite.java
index 34cf09a..bec6aac 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/test/suite/NetcdfTestSuite.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/test/suite/NetcdfTestSuite.java
@@ -38,8 +38,6 @@
     org.apache.sis.internal.netcdf.impl.ChannelDecoderTest.class,
     org.apache.sis.internal.netcdf.impl.VariableInfoTest.class,
     org.apache.sis.internal.netcdf.impl.GridInfoTest.class,
-    org.apache.sis.internal.netcdf.FeatureSetTest.class,
-    org.apache.sis.internal.netcdf.impl.FeatureSetTest.class,
     org.apache.sis.storage.netcdf.MetadataReaderTest.class,
     org.apache.sis.storage.netcdf.NetcdfStoreProviderTest.class,
     org.apache.sis.storage.netcdf.NetcdfStoreTest.class,
diff --git a/storage/sis-shapefile/pom.xml b/storage/sis-shapefile/pom.xml
index d74c5a9..12bfb6d 100644
--- a/storage/sis-shapefile/pom.xml
+++ b/storage/sis-shapefile/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/ShapefileByteReader.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/ShapefileByteReader.java
index f07a1c2..2aa1bb1 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/ShapefileByteReader.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/ShapefileByteReader.java
@@ -30,7 +30,7 @@
 import org.apache.sis.internal.shapefile.jdbc.*;
 import org.apache.sis.storage.shapefile.InvalidShapefileFormatException;
 import org.apache.sis.storage.shapefile.ShapeTypeEnum;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 import com.esri.core.geometry.*;
 
@@ -258,7 +258,7 @@
      * @param feature Feature to complete.
      * @throws InvalidShapefileFormatException if a validation problem occurs.
      */
-    public void completeFeature(Feature feature) throws InvalidShapefileFormatException {
+    public void completeFeature(AbstractFeature feature) throws InvalidShapefileFormatException {
         // insert points into some type of list
         int RecordNumber = getByteBuffer().getInt();
         @SuppressWarnings("unused")
@@ -296,7 +296,7 @@
      * Load point feature.
      * @param feature Feature to fill.
      */
-    private void loadPointFeature(Feature feature) {
+    private void loadPointFeature(AbstractFeature feature) {
         double x = getByteBuffer().getDouble();
         double y = getByteBuffer().getDouble();
         Point pnt = new Point(x, y);
@@ -307,7 +307,7 @@
      * Load polygon feature.
      * @param feature Feature to fill.
      */
-    private void loadPolygonFeature(Feature feature) {
+    private void loadPolygonFeature(AbstractFeature feature) {
         /* double xmin = */getByteBuffer().getDouble();
         /* double ymin = */getByteBuffer().getDouble();
         /* double xmax = */getByteBuffer().getDouble();
@@ -345,7 +345,6 @@
     @Deprecated // As soon as the readMultiplePolygonParts method proofs working well, this readUniquePolygonPart method can be removed and all calls be deferred to readMultiplePolygonParts.
     private Polygon readUniquePolygonPart(int numPoints) {
         /*int part = */ getByteBuffer().getInt();
-
         Polygon poly = new Polygon();
 
         // create a line from the points
@@ -429,7 +428,7 @@
      * Load polyline feature.
      * @param feature Feature to fill.
      */
-    private void loadPolylineFeature(Feature feature) {
+    private void loadPolylineFeature(AbstractFeature feature) {
         /* double xmin = */getByteBuffer().getDouble();
         /* double ymin = */getByteBuffer().getDouble();
         /* double xmax = */getByteBuffer().getDouble();
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractDbase3ByteReader.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractDbase3ByteReader.java
index e151e65..d05aec9 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractDbase3ByteReader.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractDbase3ByteReader.java
@@ -111,7 +111,7 @@
     @Override public Date getDateOfLastUpdate() {
         return toDate(this.dbaseLastUpdate);
     }
-    
+
     /**
      * Returns the first record position, in bytes, in the DBase file.
      * @return First record position.
@@ -121,13 +121,13 @@
     }
 
     /**
-     * Returns the length (in bytes) of one record in this DBase file, including the delete flag. 
+     * Returns the length (in bytes) of one record in this DBase file, including the delete flag.
      * @return Record length.
      */
     @Override public short getRecordLength() {
         return this.recordLength;
     }
-    
+
     /**
      * Returns the record count.
      * @return Record count.
@@ -248,7 +248,7 @@
 
         return(knownConversions.get(Byte.toUnsignedInt(pageCodeBinaryValue)));
     }
-    
+
     /**
      * Set a charset.
      * @param cs Charset.
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/DBase3FieldDescriptor.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/DBase3FieldDescriptor.java
index 2c58598..3f10500 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/DBase3FieldDescriptor.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/DBase3FieldDescriptor.java
@@ -32,7 +32,7 @@
 public class DBase3FieldDescriptor extends AutoChecker {
     /** Field name. */
     private byte[] fieldName = new byte[11];
-    
+
     /** Field name as String, for performance issues. */
     private String stringFieldName;
 
@@ -124,10 +124,10 @@
             while (length != 0 && Byte.toUnsignedInt(this.fieldName[length - 1]) <= ' ') {
                 length--;
             }
-            
+
             this.stringFieldName = new String(this.fieldName, 0, length);
         }
-        
+
         return this.stringFieldName;
     }
 
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/Dbase3ByteReader.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/Dbase3ByteReader.java
index 1374c85..d9fecf9 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/Dbase3ByteReader.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/Dbase3ByteReader.java
@@ -24,7 +24,7 @@
 
 import org.apache.sis.internal.shapefile.jdbc.resultset.SQLIllegalColumnIndexException;
 import org.apache.sis.internal.shapefile.jdbc.resultset.SQLNoSuchFieldException;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 /**
  * Database byte reader contract. Used to allow refactoring of core byte management of a DBase file.
@@ -114,7 +114,7 @@
      * Load a row into a feature.
      * @param feature Feature to fill.
      */
-    public void loadRowIntoFeature(Feature feature);
+    public void loadRowIntoFeature(AbstractFeature feature);
 
     /**
      * Checks if a next row is available. Warning : it may be a deleted one.
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/MappedByteReader.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/MappedByteReader.java
index 77e52e4..294be4a 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/MappedByteReader.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/MappedByteReader.java
@@ -26,7 +26,7 @@
 
 import org.apache.sis.internal.shapefile.jdbc.resultset.SQLIllegalColumnIndexException;
 import org.apache.sis.internal.shapefile.jdbc.resultset.SQLNoSuchFieldException;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -43,7 +43,7 @@
 
     /** Connection properties. */
     private Properties info;
-    
+
     /**
      * Construct a mapped byte reader on a file.
      * @param dbase3File File.
@@ -54,18 +54,18 @@
     public MappedByteReader(File dbase3File, Properties connectionInfos) throws SQLInvalidDbaseFileFormatException, SQLDbaseFileNotFoundException {
         super(dbase3File);
         this.info = connectionInfos;
-        
+
         // React to special features asked.
         if (this.info != null) {
             // Sometimes, DBF files have a wrong charset, or more often : none, and you have to specify it.
             String recordCharset = (String)this.info.get("record_charset");
-            
+
             if (recordCharset != null) {
                 Charset cs = Charset.forName(recordCharset);
                 setCharset(cs);
             }
         }
-        
+
         loadDescriptor();
     }
 
@@ -73,7 +73,7 @@
      * Load a row into a feature.
      * @param feature Feature to fill.
      */
-    @Override public void loadRowIntoFeature(Feature feature) {
+    @Override public void loadRowIntoFeature(AbstractFeature feature) {
         // TODO: ignore deleted records
         getByteBuffer().get(); // denotes whether deleted or current
         // read first part of record
@@ -102,13 +102,13 @@
         if (getByteBuffer().hasRemaining() == false) {
             return false;
         }
-        
+
         // 2) Check that the immediate next byte read isn't the EOF signal.
         byte eofCheck = getByteBuffer().get();
-        
+
         boolean isEOF = (eofCheck == 0x1A);
         this.log(Level.FINER, "log.delete_status", getRowNum(), eofCheck, isEOF ? "EOF" : "Active");
-        
+
         if (eofCheck == 0x1A) {
             return false;
         }
@@ -138,7 +138,7 @@
     public Map<String, byte[]> readNextRowAsObjects() {
         // TODO: ignore deleted records
         /* byte isDeleted = */ getByteBuffer().get(); // denotes whether deleted or current
-        
+
         // read first part of record
         HashMap<String, byte[]> fieldsValues = new HashMap<>();
 
@@ -148,18 +148,18 @@
 
             // Trim the bytes right.
             int length = data.length;
-            
+
             while (length != 0 && Byte.toUnsignedInt(data[length - 1]) <= ' ') {
                 length--;
             }
-            
+
             if (length != data.length) {
                 byte[] dataTrimmed = new byte[length];
-                
+
                 for(int index=0; index < length; index ++) {
                     dataTrimmed[index] = data[index];
                 }
-                
+
                 fieldsValues.put(fd.getName(), dataTrimmed);
             }
             else {
@@ -194,7 +194,7 @@
 
             // Translate code page value to a known charset.
             this.codePage = getByteBuffer().get();
-            
+
             if (this.charset == null) {
                 try {
                     this.charset = toCharset(this.codePage);
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/connection/DBFConnection.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/connection/DBFConnection.java
index 019e7f3..cd60673 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/connection/DBFConnection.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/connection/DBFConnection.java
@@ -317,7 +317,7 @@
     public Map<String, byte[]> readNextRowAsObjects() {
         return this.byteReader.readNextRowAsObjects();
     }
-    
+
     /**
      * Returns the record number of the last record red.
      * @return The record number.
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/resultset/DBFRecordBasedResultSet.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/resultset/DBFRecordBasedResultSet.java
index 86d799b..4751416 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/resultset/DBFRecordBasedResultSet.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/resultset/DBFRecordBasedResultSet.java
@@ -48,10 +48,10 @@
 
     /** Indicates that the last result set record matching conditions has already been returned, and a further call of next() shall throw a "no more record" exception. */
     private boolean lastResultSetRecordAlreadyReturned;
-    
+
     /** The record number of this record. */
     private int recordNumber;
-    
+
     /**
      * Constructs a result set.
      * @param stmt Parent statement.
@@ -396,7 +396,7 @@
     @Override
     public Object getObject(String columnLabel) throws SQLConnectionClosedException, SQLFeatureNotSupportedException, SQLNoSuchFieldException, SQLNotNumericException, SQLNotDateException {
         int index = -1;
-        
+
         try {
             index = findColumn(columnLabel);
             return getObject(index);
@@ -414,7 +414,7 @@
     public int getRowNum()  {
         return this.recordNumber;
     }
-    
+
     /**
      * @see java.sql.ResultSet#getShort(java.lang.String)
      * @throws SQLConnectionClosedException if the connection is closed.
@@ -469,7 +469,7 @@
         // If a non null value has been readed, convert it to the wished Charset (provided one has been given).
         DBFConnection cnt = (DBFConnection)((DBFStatement)getStatement()).getConnection();
         Charset charset = cnt.getCharset();
-        
+
         if (charset == null) {
             return new String(bytes);
         }
@@ -582,7 +582,7 @@
 
         try(DBFBuiltInMemoryResultSetForColumnsListing rs = (DBFBuiltInMemoryResultSetForColumnsListing)getFieldDesc(columnLabel, this.sql)) {
             String textValue = getString(columnLabel);
-            
+
             if (textValue == null) {
                 return null;
             }
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/InputFeatureStream.java b/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/InputFeatureStream.java
index fe3500f..c31abda 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/InputFeatureStream.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/InputFeatureStream.java
@@ -38,7 +38,7 @@
 import org.apache.sis.internal.shapefile.jdbc.statement.DBFStatement;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.storage.DataStoreClosedException;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 /**
  * Input Stream of features.
@@ -214,7 +214,7 @@
      * @throws DataStoreQueryResultException if the shapefile results cause a trouble (wrong format, for example).
      * @throws InvalidShapefileFormatException if the shapefile structure shows a problem.
      */
-    public Feature readFeature() throws DataStoreClosedException, DataStoreQueryException, DataStoreQueryResultException, InvalidShapefileFormatException {
+    public AbstractFeature readFeature() throws DataStoreClosedException, DataStoreQueryException, DataStoreQueryResultException, InvalidShapefileFormatException {
         try {
             return internalReadFeature();
         }
@@ -278,7 +278,7 @@
      * @throws InvalidShapefileFormatException if the shapefile format is invalid.
      * @throws SQLNoDirectAccessAvailableException if the underlying SQL statement requires a direct access in the shapefile, but the shapefile cannot allow it.
      */
-    private Feature internalReadFeature() throws SQLConnectionClosedException, SQLInvalidStatementException, SQLIllegalParameterException, SQLNoSuchFieldException, SQLUnsupportedParsingFeatureException, SQLNotNumericException, SQLNotDateException, SQLFeatureNotSupportedException, InvalidShapefileFormatException, SQLNoDirectAccessAvailableException {
+    private AbstractFeature internalReadFeature() throws SQLConnectionClosedException, SQLInvalidStatementException, SQLIllegalParameterException, SQLNoSuchFieldException, SQLUnsupportedParsingFeatureException, SQLNotNumericException, SQLNotDateException, SQLFeatureNotSupportedException, InvalidShapefileFormatException, SQLNoDirectAccessAvailableException {
         try {
             if (this.endOfFile) {
                 return null;
@@ -317,7 +317,7 @@
                 }
             }
 
-            Feature feature = this.featuresType.newInstance();
+            AbstractFeature feature = this.featuresType.newInstance();
             this.shapefileReader.completeFeature(feature);
             DBFDatabaseMetaData metadata = (DBFDatabaseMetaData)this.connection.getMetaData();
 
diff --git a/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/ShapeFileTest.java b/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/ShapeFileTest.java
index 86d8fbf..d4d1b84 100644
--- a/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/ShapeFileTest.java
+++ b/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/ShapeFileTest.java
@@ -28,7 +28,8 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Ignore;
 import org.junit.Test;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractAttribute;
 
 
 /**
@@ -117,12 +118,12 @@
      @Test @Ignore // TODO Adapt with another shapefile.
      public void testHandleEofNotification() throws URISyntaxException, DataStoreException {
          ShapeFile shp = new ShapeFile(path("DEPARTEMENT.SHP"));
-         Feature first = null, last = null;
+         AbstractFeature first = null, last = null;
 
          Logger log = Logger.getLogger("org.apache.sis.storage.shapefile");
 
          try(InputFeatureStream is = shp.findAll()) {
-             Feature feature = is.readFeature();
+             AbstractFeature feature = is.readFeature();
 
              // Read and retain the first and the last feature.
              while(feature != null) {
@@ -131,7 +132,7 @@
                  }
 
                  // Advice : To debug just before the last record, put a conditional breakpoint on department name "MEURTHE-ET-MOSELLE".
-                 String deptName = (String)feature.getProperty("NOM_DEPT").getValue();
+                 String deptName = (String)((AbstractAttribute) feature.getProperty("NOM_DEPT")).getValue();
                  log.info(deptName);
 
                  last = feature;
@@ -141,8 +142,8 @@
 
          assertNotNull("No record has been found in the DBase file or Shapefile.", first);
          assertNotNull("This test is not working well : last feature should always be set if any feature has been found.", last);
-         assertEquals("The first record red must be JURA department.", "JURA", first.getProperty("NOM_DEPT").getValue());
-         assertEquals("The last record red must be DEUX-SEVRES department.", "DEUX-SEVRES", last.getProperty("NOM_DEPT").getValue());
+         assertEquals("The first record red must be JURA department.", "JURA", ((AbstractAttribute) first.getProperty("NOM_DEPT")).getValue());
+         assertEquals("The last record red must be DEUX-SEVRES department.", "DEUX-SEVRES", ((AbstractAttribute) last.getProperty("NOM_DEPT")).getValue());
      }
 
      /**
@@ -155,7 +156,7 @@
          ShapeFile shp = new ShapeFile(path("ABRALicenseePt_4326_clipped.shp"));
 
          // 1) Find the third record, sequentially.
-         Feature thirdFeature;
+         AbstractFeature thirdFeature;
 
          try(InputFeatureStream isSequential = shp.findAll()) {
              isSequential.readFeature();
@@ -164,12 +165,12 @@
          }
 
          // Take one of its key fields and another field for reference, and its geometry.
-         Double sequentialAddressId = Double.valueOf((String)(thirdFeature.getProperty("ADDRID")).getValue());
-         String sequentialAddress = (String)(thirdFeature.getProperty("ADDRESS")).getValue();
+         Double sequentialAddressId = Double.valueOf((String)(((AbstractAttribute) thirdFeature.getProperty("ADDRID"))).getValue());
+         String sequentialAddress = (String)(((AbstractAttribute) thirdFeature.getProperty("ADDRESS"))).getValue();
          Object sequentialGeometry = thirdFeature.getPropertyValue("geometry");
 
          // 2) Now attempt a direct access to this feature.
-         Feature directFeature;
+         AbstractFeature directFeature;
          String sql = MessageFormat.format("SELECT * FROM ABRALicenseePt_4326_clipped WHERE ADDRID = {0,number,#0}", sequentialAddressId);
 
          try(InputFeatureStream isDirect = shp.find(sql)) {
@@ -179,8 +180,8 @@
 
          assertNotNull("The field ADDRID in the direct access feature has not been found again.", directFeature.getProperty("ADDRID"));
 
-         Double directAddressId = Double.valueOf((String)(directFeature.getProperty("ADDRID")).getValue());
-         String directAddress = (String)(directFeature.getProperty("ADDRESS")).getValue();
+         Double directAddressId = Double.valueOf((String)(((AbstractAttribute) directFeature.getProperty("ADDRID"))).getValue());
+         String directAddress = (String)(((AbstractAttribute) directFeature.getProperty("ADDRESS"))).getValue();
          Object directGeometry = directFeature.getPropertyValue("geometry");
 
          assertEquals("DBase part : direct access didn't returned the same address id than sequential access.", sequentialAddressId, directAddressId);
@@ -195,7 +196,7 @@
      */
     private void readAll(ShapeFile shp) throws DataStoreException {
         try(InputFeatureStream is = shp.findAll()) {
-            Feature feature = is.readFeature();
+            AbstractFeature feature = is.readFeature();
 
             while(feature != null) {
                 feature = is.readFeature();
diff --git a/storage/sis-sqlstore/pom.xml b/storage/sis-sqlstore/pom.xml
index 83e67fa..23748d9 100644
--- a/storage/sis-sqlstore/pom.xml
+++ b/storage/sis-sqlstore/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java
index 82afeff..ad6e228 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java
@@ -32,12 +32,12 @@
 import org.apache.sis.util.ArraysExt;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
- * Converter of {@link ResultSet} rows to {@link Feature} instances.
+ * Converter of {@link ResultSet} rows to {@code Feature} instances.
  * Each {@code FeatureAdapter} instance is specific to the set of rows given by a SQL query,
  * ignoring {@code DISTINCT}, {@code ORDER BY} and filter conditions in the {@code WHERE} clause.
  * This class does not hold JDBC resources; {@link ResultSet} must be provided by the caller.
@@ -64,7 +64,7 @@
      *
      * @see Table#featureType
      */
-    private final FeatureType featureType;
+    private final DefaultFeatureType featureType;
 
     /**
      * Attributes in feature instances, excluding operations and associations to other tables.
@@ -323,8 +323,8 @@
      * @return the feature with attribute values initialized.
      * @throws Exception if an error occurred while reading the database or converting values.
      */
-    final Feature createFeature(final InfoStatements stmts, final ResultSet result) throws Exception {
-        final Feature feature = featureType.newInstance();
+    final AbstractFeature createFeature(final InfoStatements stmts, final ResultSet result) throws Exception {
+        final AbstractFeature feature = featureType.newInstance();
         for (int i=0; i<attributes.length; i++) {
             final Column column = attributes[i];
             final Object value = column.valueGetter.getValue(stmts, result, i+1);
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAnalyzer.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAnalyzer.java
index f9ef12f..ee27bd0 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAnalyzer.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAnalyzer.java
@@ -37,7 +37,7 @@
 import org.apache.sis.util.Numbers;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -403,7 +403,7 @@
      *
      * @return the feature type.
      */
-    final FeatureType buildFeatureType() throws DataStoreException, SQLException {
+    final DefaultFeatureType buildFeatureType() throws DataStoreException, SQLException {
         String remarks = id.freeText;
         if (remarks != null) {
             feature.setDefinition(remarks);
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureIterator.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureIterator.java
index 40041f9..3f1a179 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureIterator.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureIterator.java
@@ -31,15 +31,15 @@
 import org.apache.sis.util.collection.WeakValueHashMap;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.SortOrder;
-import org.opengis.filter.SortProperty;
-import org.opengis.filter.SortBy;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.internal.geoapi.filter.SortOrder;
+import org.apache.sis.internal.geoapi.filter.SortProperty;
+import org.apache.sis.internal.geoapi.filter.SortBy;
 
 
 /**
  * Iterator over feature instances.
- * This iterator converters {@link ResultSet} rows to {@link Feature} instances.
+ * This iterator converters {@link ResultSet} rows to {@code Feature} instances.
  * Each {@code FeatureIterator} iterator is created for one specific SQL query
  * and can be used for only one iteration.
  *
@@ -53,7 +53,7 @@
  * @since   1.0
  * @module
  */
-final class FeatureIterator implements Spliterator<Feature>, AutoCloseable {
+final class FeatureIterator implements Spliterator<AbstractFeature>, AutoCloseable {
     /**
      * Characteristics of the iterator. The value returned by {@link #characteristics()}
      * must be consistent with the value given to {@code DeferredStream} constructor.
@@ -63,7 +63,7 @@
     static final int CHARACTERISTICS = NONNULL;
 
     /**
-     * The converter from a {@link ResultSet} row to a {@link Feature} instance.
+     * The converter from a {@link ResultSet} row to a {@code Feature} instance.
      */
     private final FeatureAdapter adapter;
 
@@ -116,7 +116,7 @@
      * @param count       maximum number of rows to return, or ≤ 0 for no limit.
      */
     FeatureIterator(final Table table, final Connection connection,
-             final boolean distinct, final String filter, final SortBy<? super Feature> sort,
+             final boolean distinct, final String filter, final SortBy<? super AbstractFeature> sort,
              final long offset, final long count)
             throws SQLException, InternalDataStoreException
     {
@@ -133,7 +133,7 @@
             }
             if (sort != null) {
                 String separator = " ORDER BY ";
-                for (final SortProperty<? super Feature> s : sort.getSortProperties()) {
+                for (final SortProperty<? super AbstractFeature> s : sort.getSortProperties()) {
                     builder.append(separator).appendIdentifier(s.getValueReference().getXPath());
                     final SortOrder order = s.getSortOrder();
                     if (order != null) {
@@ -158,7 +158,7 @@
      * Creates a new iterator over the dependencies of a feature.
      *
      * @param table       the source table, or {@code null} if we are creating an iterator for a dependency.
-     * @param adapter     converter from a {@link ResultSet} row to a {@link Feature} instance.
+     * @param adapter     converter from a {@link ResultSet} row to a {@code Feature} instance.
      * @param connection  connection to the database, used for creating statement.
      * @param filter      condition to append, not including the {@code WHERE} keyword.
      * @param distinct    whether the set should contain distinct feature instances.
@@ -209,7 +209,7 @@
      * @return always {@code null}.
      */
     @Override
-    public Spliterator<Feature> trySplit() {
+    public Spliterator<AbstractFeature> trySplit() {
         return null;
     }
 
@@ -217,7 +217,7 @@
      * Gives the next feature to the given consumer.
      */
     @Override
-    public boolean tryAdvance(final Consumer<? super Feature> action) {
+    public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
         try {
             return fetch(action, false);
         } catch (RuntimeException e) {
@@ -231,7 +231,7 @@
      * Gives all remaining features to the given consumer.
      */
     @Override
-    public void forEachRemaining(final Consumer<? super Feature> action) {
+    public void forEachRemaining(final Consumer<? super AbstractFeature> action) {
         try {
             fetch(action, true);
         } catch (RuntimeException e) {
@@ -245,13 +245,13 @@
      * Gives at least the next feature to the given consumer.
      * Gives all remaining features if {@code all} is {@code true}.
      *
-     * @param  action  the action to execute for each {@link Feature} instances fetched by this method.
+     * @param  action  the action to execute for each {@code Feature} instances fetched by this method.
      * @param  all     {@code true} for reading all remaining feature instances, or {@code false} for only the next one.
      * @return {@code true} if we have read an instance and {@code all} is {@code false} (so there is maybe other instances).
      */
-    private boolean fetch(final Consumer<? super Feature> action, final boolean all) throws Exception {
+    private boolean fetch(final Consumer<? super AbstractFeature> action, final boolean all) throws Exception {
         while (result.next()) {
-            final Feature feature = adapter.createFeature(spatialInformation, result);
+            final AbstractFeature feature = adapter.createFeature(spatialInformation, result);
             for (int i=0; i < dependencies.length; i++) {
                 WeakValueHashMap<?,Object> instances = null;
                 Object key = null, value = null;
@@ -299,8 +299,8 @@
      * @param  owner  if the features to fetch are components of another feature, that container feature instance.
      * @return the feature as a singleton {@code Feature} or as a {@code Collection<Feature>}.
      */
-    private Object fetchReferenced(final Feature owner) throws Exception {
-        final List<Feature> features = new ArrayList<>();
+    private Object fetchReferenced(final AbstractFeature owner) throws Exception {
+        final List<AbstractFeature> features = new ArrayList<>();
         try (ResultSet r = statement.executeQuery()) {
             result = r;
             fetch(features::add, true);
@@ -308,7 +308,7 @@
             result = null;
         }
         if (owner != null && adapter.deferredAssociation != null) {
-            for (final Feature feature : features) {
+            for (final AbstractFeature feature : features) {
                 feature.setPropertyValue(adapter.deferredAssociation, owner);
             }
         }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java
index 850cde5..2197dc9 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java
@@ -38,9 +38,9 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Filter;
-import org.opengis.filter.SortBy;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.internal.geoapi.filter.SortBy;
 
 
 /**
@@ -59,7 +59,7 @@
  * @since   1.1
  * @module
  */
-final class FeatureStream extends DeferredStream<Feature> {
+final class FeatureStream extends DeferredStream<AbstractFeature> {
     /**
      * The table which is the source of features.
      */
@@ -100,7 +100,7 @@
     /**
      * The {@code ORDER BY} clauses, or {@code null} if none.
      */
-    private SortBy<? super Feature> sort;
+    private SortBy<? super AbstractFeature> sort;
 
     /**
      * Number of rows to skip in underlying SQL query, or 0 for none.
@@ -132,7 +132,7 @@
      * Marks this stream as inactive and returns an empty stream.
      * This method is invoked when an operation resulted in an empty stream.
      */
-    private Stream<Feature> empty() {
+    private Stream<AbstractFeature> empty() {
         count = 0;
         delegate();                 // Mark this stream as inactive.
         return Stream.empty();
@@ -155,7 +155,7 @@
      * to express the filter using SQL statements.
      */
     @Override
-    public Stream<Feature> filter(final Predicate<? super Feature> predicate) {
+    public Stream<AbstractFeature> filter(final Predicate<? super AbstractFeature> predicate) {
         ArgumentChecks.ensureNonNull("predicate", predicate);
         if (predicate == Filter.include()) return this;
         if (predicate == Filter.exclude()) return empty();
@@ -182,8 +182,8 @@
          */
         final Optimization optimization = new Optimization();
         optimization.setFeatureType(table.featureType);
-        Stream<Feature> stream = this;
-        for (final Filter<? super Feature> filter : optimization.applyAndDecompose((Filter<? super Feature>) predicate)) {
+        Stream<AbstractFeature> stream = this;
+        for (final Filter<? super AbstractFeature> filter : optimization.applyAndDecompose((Filter<? super AbstractFeature>) predicate)) {
             if (filter == Filter.include()) continue;
             if (filter == Filter.exclude()) return empty();
             if (!selection.tryAppend(filterToSQL, filter)) {
@@ -200,7 +200,7 @@
      * This operation will be done with a SQL {@code DISTINCT} clause if possible.
      */
     @Override
-    public Stream<Feature> distinct() {
+    public Stream<AbstractFeature> distinct() {
         if (isPagined()) {
             return delegate().distinct();
         } else {
@@ -213,7 +213,7 @@
      * Returns an equivalent stream that is unordered.
      */
     @Override
-    public Stream<Feature> unordered() {
+    public Stream<AbstractFeature> unordered() {
         if (isPagined()) {
             return delegate().unordered();
         } else {
@@ -225,10 +225,10 @@
     /**
      * Returns an equivalent stream that is sorted by feature natural order.
      * This is defined as a matter of principle, but will cause a {@link ClassCastException} to be thrown
-     * when a terminal operation will be executed because {@link Feature} instances are not comparable.
+     * when a terminal operation will be executed because {@code Feature} instances are not comparable.
      */
     @Override
-    public Stream<Feature> sorted() {
+    public Stream<AbstractFeature> sorted() {
         if (isPagined()) {
             return delegate().sorted();
         } else {
@@ -240,11 +240,11 @@
      * Returns a stream with features of this stream sorted using the given comparator.
      */
     @Override
-    public Stream<Feature> sorted(final Comparator<? super Feature> comparator) {
+    public Stream<AbstractFeature> sorted(final Comparator<? super AbstractFeature> comparator) {
         if (isPagined() || hasComparator) {
             return delegate().sorted(comparator);
         }
-        final SortBy<? super Feature> c = SortByComparator.concatenate(sort, comparator);
+        final SortBy<? super AbstractFeature> c = SortByComparator.concatenate(sort, comparator);
         if (c != null) {
             sort = c;
             return this;
@@ -258,7 +258,7 @@
      * This operation will be done with a SQL {@code OFFSET} clause.
      */
     @Override
-    public Stream<Feature> skip(final long n) {
+    public Stream<AbstractFeature> skip(final long n) {
         // Do not require this stream to be active because this method may be invoked by `PaginedStream`.
         ArgumentChecks.ensurePositive("n", n);
         offset = Math.addExact(offset, n);
@@ -276,7 +276,7 @@
      * This operation will be done with a SQL {@code FETCH NEXT} clause.
      */
     @Override
-    public Stream<Feature> limit(final long maxSize) {
+    public Stream<AbstractFeature> limit(final long maxSize) {
         // Do not require this stream to be active because this method may be invoked by `PaginedStream`.
         ArgumentChecks.ensurePositive("maxSize", maxSize);
         if (maxSize == 0) {
@@ -292,7 +292,7 @@
      * be optimized.
      */
     @Override
-    public <R> Stream<R> map(final Function<? super Feature, ? extends R> mapper) {
+    public <R> Stream<R> map(final Function<? super AbstractFeature, ? extends R> mapper) {
         return new PaginedStream<>(super.map(mapper), this);
     }
 
@@ -391,7 +391,7 @@
      * @throws SQLException if an error occurs while executing the SQL statement.
      */
     @Override
-    protected Spliterator<Feature> createSourceIterator() throws Exception {
+    protected Spliterator<AbstractFeature> createSourceIterator() throws Exception {
         final String filter = (selection != null && !selection.isEmpty()) ? selection.toString() : null;
         selection = null;             // Let the garbage collector do its work.
 
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java
index 7d57742..5ab1f1c 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java
@@ -30,7 +30,7 @@
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -479,7 +479,7 @@
              * If we can not find an identifier that we can map to a SRID, then this loop may be
              * executed more times with CRS from EPSG database that are equal, ignore axis order.
              */
-            for (final Identifier id : candidate.getIdentifiers()) {
+            for (final ReferenceIdentifier id : candidate.getIdentifiers()) {
                 final String authority = id.getCodeSpace();
                 if (authority == null) continue;
                 final String code = id.getCode();
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SchemaModifier.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SchemaModifier.java
index 14c6917..404d1ec 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SchemaModifier.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SchemaModifier.java
@@ -22,7 +22,7 @@
 import org.apache.sis.setup.OptionKey;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -52,7 +52,7 @@
      * @throws DataStoreException if an error occurred while modifying the feature type.
      * @return the feature type to use for the specified table.
      */
-    default FeatureType editFeatureType(TableReference table, FeatureTypeBuilder feature) throws DataStoreException {
+    default DefaultFeatureType editFeatureType(TableReference table, FeatureTypeBuilder feature) throws DataStoreException {
         return feature.build();
     }
 
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClause.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClause.java
index 3d2611b..e666c38 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClause.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClause.java
@@ -26,9 +26,9 @@
 import org.apache.sis.internal.metadata.sql.SQLBuilder;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Filter;
-import org.opengis.filter.ValueReference;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
 
 
 /**
@@ -70,7 +70,7 @@
      *
      * @param  ref  reference to a property to insert in SQL statement.
      */
-    final void appendColumnName(final ValueReference<Feature,?> ref) {
+    final void appendColumnName(final ValueReference<AbstractFeature,?> ref) {
         final Column c = table.getColumn(ref.getXPath());
         if (c != null) {
             appendIdentifier(c.name);
@@ -161,7 +161,7 @@
      * @param  filter  the filter to try to convert to SQL statements.
      * @return {@code true} on success, or {@code false} in this {@code SelectionClause} is unchanged.
      */
-    final boolean tryAppend(final SelectionClauseWriter writer, final Filter<? super Feature> filter) {
+    final boolean tryAppend(final SelectionClauseWriter writer, final Filter<? super AbstractFeature> filter) {
         final int pos = buffer.length();
         if (pos != 0) {
             buffer.append(" AND ");
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClauseWriter.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClauseWriter.java
index 0e2b9fe..0e7c4c7 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClauseWriter.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SelectionClauseWriter.java
@@ -25,22 +25,21 @@
 import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import org.opengis.util.CodeList;
 import org.apache.sis.internal.filter.FunctionNames;
 import org.apache.sis.internal.filter.Visitor;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.LogicalOperator;
-import org.opengis.filter.LogicalOperatorName;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.SpatialOperatorName;
-import org.opengis.filter.BetweenComparisonOperator;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.internal.geoapi.filter.Literal;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
+import org.apache.sis.internal.geoapi.filter.LogicalOperator;
+import org.apache.sis.internal.geoapi.filter.LogicalOperatorName;
+import org.apache.sis.internal.geoapi.filter.ComparisonOperatorName;
+import org.apache.sis.internal.geoapi.filter.BinaryComparisonOperator;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.internal.geoapi.filter.BetweenComparisonOperator;
 
 
 /**
@@ -64,7 +63,7 @@
  * @since   1.1
  * @module
  */
-public class SelectionClauseWriter extends Visitor<Feature, SelectionClause> {
+public class SelectionClauseWriter extends Visitor<AbstractFeature, SelectionClause> {
     /**
      * The default instance.
      */
@@ -83,14 +82,14 @@
         setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO, new Comparison(" >= "));
         setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN,                new Comparison(" < "));
         setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,    new Comparison(" <= "));
-        setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN), (f,sql) -> {
-            final BetweenComparisonOperator<Feature>  filter = (BetweenComparisonOperator<Feature>) f;
+        setFilterHandler(ComparisonOperatorName.PROPERTY_IS_BETWEEN, (f,sql) -> {
+            final BetweenComparisonOperator<AbstractFeature>  filter = (BetweenComparisonOperator<AbstractFeature>) f;
             /* Nothing to append */  if (write(sql, filter.getExpression()))    return;
             sql.append(" BETWEEN "); if (write(sql, filter.getLowerBoundary())) return;
             sql.append(" AND ");         write(sql, filter.getUpperBoundary());
         });
         setNullAndNilHandlers((filter, sql) -> {
-            final List<Expression<? super Feature, ?>> expressions = filter.getExpressions();
+            final List<Expression<? super AbstractFeature, ?>> expressions = filter.getExpressions();
             if (expressions.size() == 1) {
                 write(sql, expressions.get(0));
                 sql.append(" IS NULL");
@@ -116,8 +115,8 @@
         setExpressionHandler(FunctionNames.Subtract, new Arithmetic(" - "));
         setExpressionHandler(FunctionNames.Divide,   new Arithmetic(" / "));
         setExpressionHandler(FunctionNames.Multiply, new Arithmetic(" * "));
-        setExpressionHandler(FunctionNames.Literal, (e,sql) -> sql.appendLiteral(((Literal<Feature,?>) e).getValue()));
-        setExpressionHandler(FunctionNames.ValueReference, (e,sql) -> sql.appendColumnName((ValueReference<Feature,?>) e));
+        setExpressionHandler(FunctionNames.Literal, (e,sql) -> sql.appendLiteral(((Literal<AbstractFeature,?>) e).getValue()));
+        setExpressionHandler(FunctionNames.ValueReference, (e,sql) -> sql.appendColumnName((ValueReference<AbstractFeature,?>) e));
         // Filters created from Filter Encoding XML can specify "PropertyName" instead of "Value reference".
         setExpressionHandler("PropertyName", getExpressionHandler(FunctionNames.ValueReference));
     }
@@ -164,7 +163,7 @@
             final boolean lowerCase = metadata.storesLowerCaseIdentifiers();
             final boolean upperCase = metadata.storesUpperCaseIdentifiers();
             for (final SpatialOperatorName type : SpatialOperatorName.values()) {
-                final BiConsumer<Filter<Feature>, SelectionClause> function = getFilterHandler(type);
+                final BiConsumer<Filter<AbstractFeature>, SelectionClause> function = getFilterHandler(type);
                 if (function instanceof Function) {
                     String name = ((Function) function).name;
                     if (lowerCase) name = name.toLowerCase(Locale.US);
@@ -207,7 +206,7 @@
      * may be truncated (later) to the length that it has the last time that it was valid.
      */
     @Override
-    protected final void typeNotFound(CodeList<?> type, Filter<Feature> filter, SelectionClause sql) {
+    protected final void typeNotFound(Enum<?> type, Filter<AbstractFeature> filter, SelectionClause sql) {
         sql.invalidate();
     }
 
@@ -216,7 +215,7 @@
      * and may be truncated (later) to the length that it has the last time that it was valid.
      */
     @Override
-    protected final void typeNotFound(String type, Expression<Feature,?> expression, SelectionClause sql) {
+    protected final void typeNotFound(String type, Expression<AbstractFeature,?> expression, SelectionClause sql) {
         sql.invalidate();
     }
 
@@ -232,8 +231,8 @@
      * @return value of {@link SelectionClause#isInvalid} flag, for allowing caller to short-circuit.
      */
     @SuppressWarnings("unchecked")
-    final boolean write(final SelectionClause sql, final Filter<? super Feature> filter) {
-        visit((Filter<Feature>) filter, sql);
+    final boolean write(final SelectionClause sql, final Filter<? super AbstractFeature> filter) {
+        visit((Filter<AbstractFeature>) filter, sql);
         return sql.isInvalid;
     }
 
@@ -249,8 +248,8 @@
      * @return value of {@link SelectionClause#isInvalid} flag, for allowing caller to short-circuit.
      */
     @SuppressWarnings("unchecked")
-    private boolean write(final SelectionClause sql, final Expression<? super Feature, ?> expression) {
-        visit((Expression<Feature, ?>) expression, sql);
+    private boolean write(final SelectionClause sql, final Expression<? super AbstractFeature, ?> expression) {
+        visit((Expression<AbstractFeature, ?>) expression, sql);
         return sql.isInvalid;
     }
 
@@ -262,7 +261,7 @@
      * @param filter    the filter for which to append the expressions.
      * @param operator  the operator to write between the expressions.
      */
-    protected final void writeBinaryOperator(final SelectionClause sql, final Filter<Feature> filter, final String operator) {
+    protected final void writeBinaryOperator(final SelectionClause sql, final Filter<AbstractFeature> filter, final String operator) {
         writeParameters(sql, filter.getExpressions(), operator, true);
     }
 
@@ -274,7 +273,7 @@
      * @param separator    the separator to insert between expression.
      * @param binary       whether the list of expressions shall contain exactly 2 elements.
      */
-    private void writeParameters(final SelectionClause sql, final List<Expression<? super Feature, ?>> expressions,
+    private void writeParameters(final SelectionClause sql, final List<Expression<? super AbstractFeature, ?>> expressions,
                                  final String separator, final boolean binary)
     {
         final int n = expressions.size();
@@ -299,7 +298,7 @@
      * The filter can contain an arbitrary amount of operands, all separated by the same keyword.
      * All operands are grouped between parenthesis.
      */
-    private final class Logic implements BiConsumer<Filter<Feature>, SelectionClause> {
+    private final class Logic implements BiConsumer<Filter<AbstractFeature>, SelectionClause> {
         /**
          * The {@code AND}, {@code OR} or {@code NOT} keyword.
          * Shall contain a trailing space and eventually a leading space.
@@ -319,9 +318,9 @@
         }
 
         /** Invoked when a logical filter needs to be converted to SQL clause. */
-        @Override public void accept(final Filter<Feature> f, final SelectionClause sql) {
-            final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) f;
-            final List<Filter<? super Feature>> operands = filter.getOperands();
+        @Override public void accept(final Filter<AbstractFeature> f, final SelectionClause sql) {
+            final LogicalOperator<AbstractFeature> filter = (LogicalOperator<AbstractFeature>) f;
+            final List<Filter<? super AbstractFeature>> operands = filter.getOperands();
             final int n = operands.size();
             if (unary ? (n != 1) : (n == 0)) {
                 sql.invalidate();
@@ -347,7 +346,7 @@
      * into SQL clauses. The filter is expected to contain exactly two operands, otherwise the
      * SQL is declared invalid.
      */
-    private final class Comparison implements BiConsumer<Filter<Feature>, SelectionClause> {
+    private final class Comparison implements BiConsumer<Filter<AbstractFeature>, SelectionClause> {
         /** The comparison operator symbol. */
         private final String operator;
 
@@ -357,8 +356,8 @@
         }
 
         /** Invoked when a comparison needs to be converted to SQL clause. */
-        @Override public void accept(final Filter<Feature> f, final SelectionClause sql) {
-            final BinaryComparisonOperator<Feature> filter = (BinaryComparisonOperator<Feature>) f;
+        @Override public void accept(final Filter<AbstractFeature> f, final SelectionClause sql) {
+            final BinaryComparisonOperator<AbstractFeature> filter = (BinaryComparisonOperator<AbstractFeature>) f;
             if (filter.isMatchingCase()) {
                 writeBinaryOperator(sql, filter, operator);
             } else {
@@ -374,7 +373,7 @@
      * Handler for converting {@code +}, {@code -}, {@code *} or {@code /} filter into SQL clauses.
      * The filter is expected to contain exactly two operands, otherwise the SQL is declared invalid.
      */
-    private final class Arithmetic implements BiConsumer<Expression<Feature,?>, SelectionClause> {
+    private final class Arithmetic implements BiConsumer<Expression<AbstractFeature,?>, SelectionClause> {
         /** The arithmetic operator symbol. */
         private final String operator;
 
@@ -384,7 +383,7 @@
         }
 
         /** Invoked when an arithmetic expression needs to be converted to SQL clause. */
-        @Override public void accept(final Expression<Feature,?> expression, final SelectionClause sql) {
+        @Override public void accept(final Expression<AbstractFeature,?> expression, final SelectionClause sql) {
             writeParameters(sql, expression.getParameters(), operator, true);
         }
     }
@@ -397,7 +396,7 @@
      * This method stops immediately if a parameter can not be expressed in SQL, leaving
      * the trailing part of the SQL in an invalid state.
      */
-    private final class Function implements BiConsumer<Filter<Feature>, SelectionClause> {
+    private final class Function implements BiConsumer<Filter<AbstractFeature>, SelectionClause> {
         /** Name the function. */
         final String name;
 
@@ -407,7 +406,7 @@
         }
 
         /** Writes the function as an SQL statement. */
-        @Override public void accept(final Filter<Feature> filter, final SelectionClause sql) {
+        @Override public void accept(final Filter<AbstractFeature> filter, final SelectionClause sql) {
             sql.append(name);
             writeParameters(sql, filter.getExpressions(), ", ", false);
         }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
index 6e5b40c..82d237c 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
@@ -38,17 +38,16 @@
 import org.apache.sis.util.Debug;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAssociationRole;
 
 
 /**
  * Description of a table in the database, including columns, primary keys and foreigner keys.
- * This class contains a {@link FeatureType} inferred from the table structure.
- * The {@link FeatureType} contains an {@link AttributeType} for each table column,
- * except foreigner keys which are represented by {@link FeatureAssociationRole}s.
+ * This class contains a {@code FeatureType} inferred from the table structure.
+ * The {@code FeatureType} contains an {@link AttributeType} for each table column,
+ * except foreigner keys which are represented by {@code FeatureAssociationRole}s.
  *
  * <h2>Multi-threading</h2>
  * This class is immutable (except for the cache) and safe for concurrent use by many threads.
@@ -75,7 +74,7 @@
      *
      * @see #getType()
      */
-    final FeatureType featureType;
+    final DefaultFeatureType featureType;
 
     /**
      * The SQL query to execute for fetching data, or {@code null} for querying the table identified by {@link #name}.
@@ -99,7 +98,7 @@
      * This array shall not be modified after construction.
      *
      * <p>Columns may have alias if it was necessary to avoid name collisions.
-     * The alias is given by {@link Column#propertyName} and will be the name used in {@link FeatureType}.</p>
+     * The alias is given by {@link Column#propertyName} and will be the name used in {@code FeatureType}.</p>
      */
     final Column[] attributes;
 
@@ -138,7 +137,7 @@
     private Map<String,Column> attributeToColumns;
 
     /**
-     * The converter of {@link ResultSet} rows to {@link Feature} instances.
+     * The converter of {@link ResultSet} rows to {@code Feature} instances.
      * Created when first needed.
      *
      * @see #adapter(Connection)
@@ -191,7 +190,7 @@
      *
      * @todo This constructor is not yet used because it is an unfinished work.
      *       We need to invent some mechanism for using a subset of the columns.
-     *       A starting point is {@link org.apache.sis.storage.FeatureQuery#expectedType(FeatureType)}.
+     *       A starting point is {@link org.apache.sis.storage.FeatureQuery#expectedType(DefaultFeatureType)}.
      */
     Table(final Table parent) {
         super(parent.listeners, false);
@@ -231,8 +230,8 @@
                      * have been set to association names. If `ClassCastException` occurs here, it is a bug
                      * in our object constructions.
                      */
-                    final FeatureAssociationRole association =
-                            (FeatureAssociationRole) featureType.getProperty(relation.propertyName);
+                    final DefaultAssociationRole association =
+                            (DefaultAssociationRole) featureType.getProperty(relation.propertyName);
 
                     final Table table = tables.get(association.getValueType().getName());
                     if (table == null) {
@@ -316,7 +315,7 @@
      * Returns the feature type inferred from the database structure analysis.
      */
     @Override
-    public final FeatureType getType() {
+    public final DefaultFeatureType getType() {
         return featureType;
     }
 
@@ -459,7 +458,7 @@
     }
 
     /**
-     * Returns the converter of {@link ResultSet} rows to {@link Feature} instances.
+     * Returns the converter of {@link ResultSet} rows to {@code Feature} instances.
      * The converter is created the first time that this method is invoked, then cached.
      *
      * @param  connection  source of database metadata to use if the adapter needs to be created.
@@ -479,7 +478,7 @@
      * @throws DataStoreException if an error occurred while creating the stream.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) throws DataStoreException {
+    public Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
         return new FeatureStream(this, parallel);
     }
 }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/package-info.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/package-info.java
index 145b81c..91b15cd 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/package-info.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/package-info.java
@@ -17,7 +17,7 @@
 
 
 /**
- * Build {@link org.opengis.feature.FeatureType}s by inspection of database schemas.
+ * Build {@link org.apache.sis.feature.DefaultFeatureType}s by inspection of database schemas.
  * The work done here is similar to reverse engineering.
  *
  * <STRONG>Do not use!</STRONG>
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ExtendedClauseWriter.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ExtendedClauseWriter.java
index 70d652f..a039481 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ExtendedClauseWriter.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ExtendedClauseWriter.java
@@ -19,7 +19,7 @@
 import org.apache.sis.internal.sql.feature.SelectionClauseWriter;
 
 // Branch-dependent imports
-import org.opengis.filter.SpatialOperatorName;
+import org.apache.sis.internal.geoapi.filter.SpatialOperatorName;
 
 
 /**
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
index c2fc534..bf63bf8 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
@@ -83,7 +83,7 @@
     private final GeometryLibrary geomLibrary;
 
     /**
-     * The result of inspecting database schema for deriving {@link org.opengis.feature.FeatureType}s.
+     * The result of inspecting database schema for deriving {@code FeatureType}s.
      * Created when first needed. May be discarded and recreated if the store needs a refresh.
      */
     private Database<?> model;
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java
index 89cdda1..09f6917 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java
@@ -19,11 +19,11 @@
 /**
  * Data store capable to read and create features from a JDBC connection to a database.
  * {@link org.apache.sis.storage.sql.SQLStore} takes a one or more tables at construction time.
- * Each enumerated table is represented by a {@link org.opengis.feature.FeatureType}.
- * Each row in those table represents a {@link org.opengis.feature.Feature} instance.
- * Each relation defined by a foreigner key is represented by an {@link org.opengis.feature.FeatureAssociationRole}
+ * Each enumerated table is represented by a {@code FeatureType}.
+ * Each row in those table represents a {@code Feature} instance.
+ * Each relation defined by a foreigner key is represented by an {@code FeatureAssociationRole}
  * to another feature (with transitive dependencies automatically resolved), and the other columns are represented
- * by {@link org.opengis.feature.AttributeType}.
+ * by {@code AttributeType}.
  *
  * <p>The storage of spatial features in SQL databases is described by the
  * <a href="https://www.ogc.org/standards/sfs">OGC Simple feature access - Part 2: SQL option</a>
@@ -35,7 +35,7 @@
  * <p>A subset of features can be obtained by applying filters on the stream returned by
  * {@link org.apache.sis.storage.FeatureSet#features(boolean)}.
  * While the filter can be any {@link java.util.function.Predicate},
- * performances will be much better if they are instances of {@link org.opengis.filter.Filter}
+ * performances will be much better if they are instances of {@link org.apache.sis.filter.Filter}
  * because Apache SIS will know how to translate some of them to SQL statements.</p>
  *
  * <p>In filter expressions like {@code ST_Intersects(A,B)} where the <var>A</var> and <var>B</var> parameters are
diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java
index 357984f..34c97d7 100644
--- a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java
+++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/SelectionClauseWriterTest.java
@@ -32,11 +32,9 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.SpatialOperator;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Filter;
 
 
 /**
@@ -52,7 +50,7 @@
     /**
      * The factory to use for creating the filter objects.
      */
-    private final FilterFactory<Feature,Object,Object> FF;
+    private final DefaultFilterFactory<AbstractFeature,Object,Object> FF;
 
     /**
      * A dummy table for testing purpose.
@@ -92,7 +90,7 @@
      * Creates a filter without geometry and verifies that it is translated to the expected SQL fragment.
      */
     private void testSimpleFilter() {
-        final Filter<Feature> filter = FF.and(
+        final Filter<AbstractFeature> filter = FF.and(
                 FF.greater(FF.property("ALPHA"), FF.property("BETA")),
                 FF.or(FF.isNull(FF.property("GAMMA")),
                       FF.equal(FF.literal(3.14), FF.property("PI"))));
@@ -104,7 +102,7 @@
      * Creates a filter with a geometric objects and verifies that it is translated to the expected SQL fragment.
      */
     private void testGeometricFilter() {
-        final SpatialOperator<Feature> filter = FF.intersects(
+        final Filter<AbstractFeature> filter = FF.intersects(
                 FF.property("ALPHA"),
                 FF.literal(new GeneralEnvelope(new double[] {-12.3, 43.3}, new double[] {2.1, 51.7})));
 
@@ -117,7 +115,7 @@
      * This method add a CRS on a property for testing purpose.
      */
     @Override
-    public FeatureType editFeatureType(final TableReference table, final FeatureTypeBuilder feature) {
+    public DefaultFeatureType editFeatureType(final TableReference table, final FeatureTypeBuilder feature) {
         assertEquals("",     table.catalog);
         assertEquals("APP",  table.schema);
         assertEquals("TEST", table.table);
@@ -132,7 +130,7 @@
         final GeneralEnvelope bbox = new GeneralEnvelope(HardCodedCRS.WGS84_LATITUDE_FIRST);
         bbox.setEnvelope(-10, 20, -5, 25);
 
-        Filter<Feature> filter = FF.intersects(FF.property("BETA"), FF.literal(bbox));
+        Filter<AbstractFeature> filter = FF.intersects(FF.property("BETA"), FF.literal(bbox));
         final Optimization optimization = new Optimization();
         optimization.setFeatureType(table.featureType);
         verifySQL(optimization.apply(filter), "ST_Intersects(\"BETA\", " +
@@ -143,7 +141,7 @@
      * Formats the given filter as a SQL {@code WHERE} statement body
      * and verifies that the result is equal to the expected string.
      */
-    private void verifySQL(final Filter<? super Feature> filter, final String expected) {
+    private void verifySQL(final Filter<? super AbstractFeature> filter, final String expected) {
         final SelectionClause sql = new SelectionClause(table);
         assertTrue(sql.tryAppend(SelectionClauseWriter.DEFAULT, filter));
         assertEquals(expected, sql.toString());
diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java
index abcf98d..4747684 100644
--- a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java
+++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java
@@ -49,7 +49,7 @@
 import org.junit.Test;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 // Optional dependencies
 import org.locationtech.jts.geom.Point;
@@ -111,7 +111,7 @@
                  * Tests through public API.
                  */
                 final FeatureSet resource = store.findResource("SpatialData");
-                try (Stream<Feature> features = resource.features(false)) {
+                try (Stream<AbstractFeature> features = resource.features(false)) {
                     features.forEach(PostgresTest::validate);
                 }
                 final Envelope envelope = resource.getEnvelope().get();
@@ -165,7 +165,7 @@
      * Invoked for each feature instances for performing some checks on the feature.
      * This method performs only a superficial verification of geometries.
      */
-    private static void validate(final Feature feature) {
+    private static void validate(final AbstractFeature feature) {
         final String       filename = feature.getPropertyValue("filename").toString();
         final Geometry     geometry = (Geometry) feature.getPropertyValue("geometry");
         final GridCoverage raster   = (GridCoverage) feature.getPropertyValue("image");
diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
index 5756e44..c6603b3 100644
--- a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
+++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
@@ -39,13 +39,11 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.SortOrder;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultAssociationRole;
 
 
 /**
@@ -100,12 +98,12 @@
      * This feature should appear twice, and all those occurrences should use the exact same instance.
      * We use that for verifying the {@code Table.instanceForPrimaryKeys} caching.
      */
-    private Feature canada;
+    private AbstractFeature canada;
 
     /**
      * Factory to use for creating filter objects.
      */
-    private final FilterFactory<Feature,Object,Object> FF;
+    private final DefaultFilterFactory<AbstractFeature,Object,Object> FF;
 
     /**
      * Creates a new test.
@@ -204,7 +202,7 @@
         try (SQLStore store = new SQLStore(new SQLStoreProvider(), connector, table)) {
             verifyFeatureTypes(store);
             final Map<String,Integer> countryCount = new HashMap<>();
-            try (Stream<Feature> features = store.findResource("Cities").features(false)) {
+            try (Stream<AbstractFeature> features = store.findResource("Cities").features(false)) {
                 features.forEach((f) -> verifyContent(f, countryCount));
             }
             assertEquals(Integer.valueOf(2), countryCount.remove("CAN"));
@@ -258,9 +256,9 @@
     /**
      * Verifies the result of analyzing the structure of the {@code "Cities"} table.
      */
-    private static void verifyFeatureType(final FeatureType type, final String[] expectedNames, final Object[] expectedTypes) {
+    private static void verifyFeatureType(final DefaultFeatureType type, final String[] expectedNames, final Object[] expectedTypes) {
         int i = 0;
-        for (PropertyType pt : type.getProperties(false)) {
+        for (AbstractIdentifiedType pt : type.getProperties(false)) {
             if (i >= expectedNames.length) {
                 fail("Returned feature-type contains more properties than expected. Example: " + pt.getName());
             }
@@ -271,10 +269,10 @@
                 final Object value;
                 if (expectedType instanceof Class<?>) {
                     label = "attribute type";
-                    value = ((AttributeType<?>) pt).getValueClass();
+                    value = ((DefaultAttributeType<?>) pt).getValueClass();
                 } else {
                     label = "association type";
-                    value = ((FeatureAssociationRole) pt).getValueType().getName().toString();
+                    value = ((DefaultAssociationRole) pt).getValueType().getName().toString();
                 }
                 assertEquals(label, expectedType, value);
             }
@@ -290,7 +288,7 @@
      * @param  feature       a feature returned by the stream.
      * @param  countryCount  number of time that the each country has been seen while iterating over the cities.
      */
-    private void verifyContent(final Feature feature, final Map<String,Integer> countryCount) {
+    private void verifyContent(final AbstractFeature feature, final Map<String,Integer> countryCount) {
         final String city = feature.getPropertyValue("native_name").toString();
         final City c;
         boolean isCanada = false;
@@ -314,7 +312,7 @@
          */
         assertEquals("country", c.countryName, getIndirectPropertyValue(feature, "country", "native_name"));
         if (isCanada) {
-            final Feature f = (Feature) feature.getPropertyValue("country");
+            final AbstractFeature f = (AbstractFeature) feature.getPropertyValue("country");
             if (canada == null) {
                 canada = f;
             } else {
@@ -331,7 +329,7 @@
         assertEquals("parks.length", c.parks.length, actualParks.size());
         final Collection<String> expectedParks = new HashSet<>(Arrays.asList(c.parks));
         for (final Object park : actualParks) {
-            final Feature pf = (Feature) park;
+            final AbstractFeature pf = (AbstractFeature) park;
             final String npn = (String) pf.getPropertyValue("native_name");
             final String epn = (String) pf.getPropertyValue("english_name");
             assertNotNull("park.native_name",  npn);
@@ -351,11 +349,11 @@
     /**
      * Follows an association in the given feature.
      */
-    private static Object getIndirectPropertyValue(final Feature feature, final String p1, final String p2) {
+    private static Object getIndirectPropertyValue(final AbstractFeature feature, final String p1, final String p2) {
         final Object dependency = feature.getPropertyValue(p1);
         assertNotNull(p1, dependency);
-        assertInstanceOf(p1, Feature.class, dependency);
-        return ((Feature) dependency).getPropertyValue(p2);
+        assertInstanceOf(p1, AbstractFeature.class, dependency);
+        return ((AbstractFeature) dependency).getPropertyValue(p2);
     }
 
     /**
@@ -381,21 +379,19 @@
         final FeatureSet   parks = dataset.findResource("Parks");
         final FeatureQuery query = new FeatureQuery();
         query.setProjection(FF.property(desiredProperty));
-        query.setSortBy(FF.sort(FF.property("country"),       SortOrder.DESCENDING),
-                        FF.sort(FF.property(desiredProperty), SortOrder.ASCENDING));
         final FeatureSet subset = parks.subset(query);
         /*
-         * Verify that all features have the expected property, then verify the sorted values.
+         * Verify that all features have the expected property.
          */
         final Object[] values;
-        try (Stream<Feature> features = subset.features(false)) {
+        try (Stream<AbstractFeature> features = subset.features(false)) {
             values = features.map(f -> {
-                final PropertyType p = TestUtilities.getSingleton(f.getType().getProperties(true));
+                final AbstractIdentifiedType p = TestUtilities.getSingleton(f.getType().getProperties(true));
                 assertEquals("Feature has wrong property.", desiredProperty, p.getName().toString());
                 return f.getPropertyValue(desiredProperty);
             }).toArray();
         }
-        assertArrayEquals("Values are not sorted as expected.", expectedValues, values);
+        assertEquals(new HashSet<>(Arrays.asList(expectedValues)), new HashSet<>(Arrays.asList(values)));
     }
 
     /**
@@ -436,7 +432,7 @@
         final FeatureQuery query  = new FeatureQuery();
         query.setSelection(FF.equal(FF.property("country"), FF.literal("CAN")));
         final Object[] names;
-        try (Stream<Feature> features = cities.subset(query).features(false)) {
+        try (Stream<AbstractFeature> features = cities.subset(query).features(false)) {
             names = features.map(f -> f.getPropertyValue(desiredProperty)).toArray();
         }
         assertSetEquals(Arrays.asList(expectedValues), Arrays.asList(names));
@@ -457,7 +453,7 @@
         query.setSelection(FF.equal(FF.property("sis:identifier"), FF.literal("CAN")));
         final String executionMode;
         final Object[] names;
-        try (Stream<Feature> features = countries.subset(query).features(false)) {
+        try (Stream<AbstractFeature> features = countries.subset(query).features(false)) {
             executionMode = features.toString();
             names = features.map(f -> f.getPropertyValue(desiredProperty)).toArray();
         }
@@ -479,7 +475,7 @@
      * @param  cities  a feature set containing all cities defined for the test class.
      */
     private void verifyStreamOperations(final FeatureSet cities) throws DataStoreException {
-        try (Stream<Feature> features = cities.features(false)) {
+        try (Stream<AbstractFeature> features = cities.features(false)) {
             final AtomicInteger peekCount = new AtomicInteger();
             final AtomicInteger mapCount  = new AtomicInteger();
             final long actualPopulations = features.peek(f -> peekCount.incrementAndGet())
@@ -516,7 +512,7 @@
         {
             final FeatureSet cities = store.findResource("LargeCities");
             final Map<String,Integer> countryCount = new HashMap<>();
-            try (Stream<Feature> features = cities.features(false)) {
+            try (Stream<AbstractFeature> features = cities.features(false)) {
                 features.forEach((f) -> verifyContent(f, countryCount));
             }
             assertEquals(Integer.valueOf(1), countryCount.remove("CAN"));
@@ -532,28 +528,27 @@
      */
     private void verifyNestedSQLQuery(final StorageConnector connector) throws Exception {
         try (SQLStore store = new SQLStore(null, connector, ResourceDefinition.query("MyParks",
-                "SELECT * FROM " + SCHEMA + ".\"Parks\"")))
+                "SELECT * FROM " + SCHEMA + ".\"Parks\" ORDER BY \"native_name\" DESC")))
         {
             final FeatureSet parks = store.findResource("MyParks");
             /*
              * Add a filter for parks in France.
              */
             final FeatureQuery query = new FeatureQuery();
-            query.setSortBy(FF.sort(FF.property("native_name"), SortOrder.DESCENDING));
             query.setSelection(FF.equal(FF.property("country"), FF.literal("FRA")));
             query.setProjection(FF.property("native_name"));
             final FeatureSet frenchParks = parks.subset(query);
             /*
              * Verify the feature type.
              */
-            final PropertyType property = TestUtilities.getSingleton(frenchParks.getType().getProperties(true));
+            final AbstractIdentifiedType property = TestUtilities.getSingleton(frenchParks.getType().getProperties(true));
             assertEquals("native_name", property.getName().toString());
-            assertEquals(String.class, ((AttributeType<?>) property).getValueClass());
+            assertEquals(String.class, ((DefaultAttributeType<?>) property).getValueClass());
             /*
              * Verify the values.
              */
             final Object[] result;
-            try (Stream<Feature> fs = frenchParks.features(false)) {
+            try (Stream<AbstractFeature> fs = frenchParks.features(false)) {
                 result = fs.map(f -> f.getPropertyValue("native_name")).toArray();
             }
             assertArrayEquals(new String[] {"Jardin du Luxembourg", "Jardin des Tuileries"}, result);
@@ -573,8 +568,8 @@
                 "OFFSET 2 ROWS FETCH NEXT 3 ROWS ONLY")))
         {
             final FeatureSet parks = store.findResource("MyQuery");
-            final FeatureType type = parks.getType();
-            final AttributeType<?> property = (AttributeType<?>) TestUtilities.getSingleton(type.getProperties(true));
+            final DefaultFeatureType type = parks.getType();
+            final DefaultAttributeType<?> property = (DefaultAttributeType<?>) TestUtilities.getSingleton(type.getProperties(true));
             assertEquals("Property name should be label defined in query", "title", property.getName().toString());
             assertEquals("Attribute should be a string", String.class, property.getValueClass());
             assertEquals("Column should be nullable.", 0, property.getMinimumOccurs());
@@ -602,7 +597,7 @@
      * then returns the values of the "title" property of all features.
      */
     private static String[] getTitles(final FeatureSet parks, final long skip, final long limit) throws DataStoreException {
-        try (Stream<Feature> in = parks.features(false).skip(skip).limit(limit)) {
+        try (Stream<AbstractFeature> in = parks.features(false).skip(skip).limit(limit)) {
             return in.map(f -> f.getPropertyValue("title").toString()).toArray(String[]::new);
         }
     }
@@ -616,7 +611,7 @@
                 "SELECT \"country\" FROM " + SCHEMA + ".\"Parks\" ORDER BY \"country\"")))
         {
             final FeatureSet countries = store.findResource("Countries");
-            try (Stream<Feature> features = countries.features(false).distinct()) {
+            try (Stream<AbstractFeature> features = countries.features(false).distinct()) {
                 expected = features.map(f -> f.getPropertyValue("country")).toArray();
             }
         }
diff --git a/storage/sis-storage/pom.xml b/storage/sis-storage/pom.xml
index 987e1b1..a4ccfe2 100644
--- a/storage/sis-storage/pom.xml
+++ b/storage/sis-storage/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java
index 44870f5..5da4737 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java
@@ -32,7 +32,7 @@
 import org.apache.sis.storage.AbstractFeatureSet;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -147,7 +147,7 @@
         final MetadataBuilder metadata = new MetadataBuilder();
         metadata.addDefaultMetadata(this, listeners);
         for (final FeatureSet fs : dependencies()) {
-            final FeatureType type = fs.getType();
+            final DefaultFeatureType type = fs.getType();
             metadata.addSource(fs.getMetadata(), ScopeCode.FEATURE_TYPE,
                     (type == null) ? null : new CharSequence[] {type.getName().toInternationalString()});
         }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Capability.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Capability.java
index 1b85011..6ae6279 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Capability.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Capability.java
@@ -25,8 +25,8 @@
 import org.apache.sis.util.collection.BackingStoreException;
 
 // Branch-dependent imports
-import org.opengis.util.InternationalString;
-import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.distribution.Format;
+import org.apache.sis.util.iso.Types;
 
 
 /**
@@ -105,9 +105,13 @@
              * Get a title for the format, followed by the short name between parenthesis
              * if it does not repeat the main title.
              */
+            final Format format = provider.getFormat();
             String title, complement;
             try {
-                title = title(provider.getFormat().getFormatSpecificationCitation()).toString(locale);
+                title = Types.toString(format.getSpecification(), locale);
+                if (title == null) {
+                    title = Types.toString(format.getName(), locale);
+                }
                 complement = provider.getShortName();
             } catch (BackingStoreException e) {
                 title = provider.getShortName();
@@ -121,16 +125,4 @@
         }
         return list;
     }
-
-    /**
-     * Returns the title or alternate title of the given citation, or "untitled" if none.
-     */
-    private static InternationalString title(final Citation specification) {
-        final InternationalString title = specification.getTitle();
-        if (title != null) return title;
-        for (final InternationalString t : specification.getAlternateTitles()) {
-            if (t != null) return t;
-        }
-        return Vocabulary.formatInternational(Vocabulary.Keys.Untitled);
-    }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java
index 1b6645f..650627c 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java
@@ -35,8 +35,8 @@
 import org.apache.sis.storage.Query;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -65,7 +65,7 @@
     /**
      * The most specific feature type common to all feature sets in the {@linkplain #sources} list.
      */
-    private final FeatureType commonType;
+    private final DefaultFeatureType commonType;
 
     /**
      * Creates a new concatenated feature set with the same types than the given feature set,
@@ -94,7 +94,7 @@
             ArgumentChecks.ensureNonNullElement("sources", i, sources[i]);
         }
         this.sources = UnmodifiableArrayList.wrap(sources);
-        final FeatureType[] types = new FeatureType[sources.length];
+        final DefaultFeatureType[] types = new DefaultFeatureType[sources.length];
         for (int i=0; i<types.length; i++) {
             types[i] = sources[i].getType();
         }
@@ -164,7 +164,7 @@
      * @return the common type of all features returned by this set.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return commonType;
     }
 
@@ -209,7 +209,7 @@
      * @return all features contained in this dataset.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) {
+    public Stream<AbstractFeature> features(final boolean parallel) {
         final Stream<FeatureSet> sets = parallel ? sources.parallelStream() : sources.stream();
         return sets.flatMap(set -> {
             try {
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FeatureCatalogBuilder.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FeatureCatalogBuilder.java
index 5b38bf3..bf883d1 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FeatureCatalogBuilder.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FeatureCatalogBuilder.java
@@ -23,12 +23,12 @@
 import org.apache.sis.metadata.iso.DefaultMetadata;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
  * Helper methods for the feature metadata created by {@code DataStore} implementations.
- * This is a convenience class for chaining {@link #addFeatureType(FeatureType, long)}
+ * This is a convenience class for chaining {@code addFeatureType(FeatureType, long)}
  * method calls with {@link FeatureNaming#add(DataStore, GenericName, Object)}.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -51,7 +51,7 @@
      * {@code DataStore} implementations can keep the reference to this {@code FeatureNaming}
      * after the {@link #build(boolean)} method has been invoked.
      */
-    public final FeatureNaming<FeatureType> features;
+    public final FeatureNaming<DefaultFeatureType> features;
 
     /**
      * Creates a new builder for the given data store.
@@ -74,9 +74,9 @@
      * @param  type  the feature type to add, or {@code null}.
      * @throws IllegalNameException if a feature of the same name has already been added.
      *
-     * @see #addFeatureType(FeatureType, long)
+     * @see #addFeatureType(DefaultFeatureType, long)
      */
-    public final void define(final FeatureType type) throws IllegalNameException {
+    public final void define(final DefaultFeatureType type) throws IllegalNameException {
         final GenericName name = addFeatureType(type, -1);
         if (name != null) {
             features.add(store, name, type);
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 5906016..e27e27d 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
@@ -38,15 +38,13 @@
 import org.apache.sis.util.collection.Containers;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.Expression;
-import org.opengis.filter.BinaryComparisonOperator;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
 import org.apache.sis.filter.DefaultFilterFactory;
+import org.apache.sis.internal.geoapi.filter.BinaryComparisonOperator;
 
 
 /**
@@ -137,7 +135,7 @@
     /**
      * The type of features included in this set. Contains two associations as described in class javadoc.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType type;
 
     /**
      * The first source of features.
@@ -178,12 +176,12 @@
      * This condition specifies also if the comparison is {@linkplain BinaryComparisonOperator#isMatchingCase() case
      * sensitive} and {@linkplain BinaryComparisonOperator#getMatchAction() how to compare multi-values}.
      */
-    public final BinaryComparisonOperator<? super Feature> condition;
+    public final BinaryComparisonOperator<? super AbstractFeature> condition;
 
     /**
      * The factory to use for creating {@code Query} expressions for retrieving subsets of feature sets.
      */
-    private final FilterFactory<Feature,?,?> factory;
+    private final DefaultFilterFactory<AbstractFeature,?,?> factory;
 
     /**
      * Creates a new feature set joining the two given sets. The {@code featureInfo} map defines the name,
@@ -204,19 +202,19 @@
      * @param  rightAlias   name of the associations to the {@code right} features, or {@code null} for a default name.
      * @param  joinType     whether values on both sides are required (inner join), or only one side (outer join).
      * @param  condition    join condition as <var>property from left feature</var> = <var>property from right feature</var>.
-     * @param  featureInfo  information about the {@link FeatureType} of this feature set.
+     * @param  featureInfo  information about the {@code FeatureType} of this feature set.
      * @throws DataStoreException if an error occurred while creating the feature set.
      */
     public JoinFeatureSet(final StoreListeners parent,
                           final FeatureSet left,  String leftAlias,
                           final FeatureSet right, String rightAlias,
-                          final Type joinType, final BinaryComparisonOperator<? super Feature> condition,
+                          final Type joinType, final BinaryComparisonOperator<? super AbstractFeature> condition,
                           Map<String,?> featureInfo)
             throws DataStoreException
     {
         super(parent);
-        final FeatureType leftType  = left.getType();
-        final FeatureType rightType = right.getType();
+        final DefaultFeatureType leftType  = left.getType();
+        final DefaultFeatureType rightType = right.getType();
         final GenericName leftName  = leftType.getName();
         final GenericName rightName = rightType.getName();
         if (leftAlias  == null) leftAlias  = leftName.toString();
@@ -233,7 +231,7 @@
          * We could build the FeatureType only when first needed, but the type is required by the iterators.
          * Since we are going to need the type for any use of this JoinFeatureSet, better to create it now.
          */
-        PropertyType[] properties = new PropertyType[] {
+        AbstractIdentifiedType[] properties = new AbstractIdentifiedType[] {
             new DefaultAssociationRole(name(leftAlias),  leftType,  joinType.minimumOccurs(false), 1),
             new DefaultAssociationRole(name(rightAlias), rightType, joinType.minimumOccurs(true),  1)
         };
@@ -241,7 +239,7 @@
         if (identifierDelimiter != null && AttributeConvention.hasIdentifier(leftType)
                                         && AttributeConvention.hasIdentifier(rightType))
         {
-            final Operation identifier = FeatureOperations.compound(
+            final AbstractOperation identifier = FeatureOperations.compound(
                     name(AttributeConvention.IDENTIFIER_PROPERTY), identifierDelimiter,
                     Containers.property(featureInfo, "identifierPrefix", String.class),
                     Containers.property(featureInfo, "identifierSuffix", String.class), properties);
@@ -292,7 +290,7 @@
      * @return a description of properties that are common to all features in this dataset.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -304,7 +302,7 @@
      * @throws DataStoreException if an error occurred while creating the stream.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) throws DataStoreException {
+    public Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
         final Iterator it = new Iterator();
         return StreamSupport.stream(it, parallel).onClose(it);
     }
@@ -313,13 +311,13 @@
      * Creates a new features containing an association to the two given features.
      * The {@code main} feature can not be null (this is not verified).
      */
-    private Feature join(Feature main, Feature filtered) {
+    private AbstractFeature join(AbstractFeature main, AbstractFeature filtered) {
         if (swapSides) {
-            final Feature t = main;
+            final AbstractFeature t = main;
             main = filtered;
             filtered = t;
         }
-        final Feature f = type.newInstance();
+        final AbstractFeature f = type.newInstance();
         f.setPropertyValue(leftName,  main);
         f.setPropertyValue(rightName, filtered);
         return f;
@@ -329,7 +327,7 @@
      * Iterator over the features resulting from the inner or outer join operation.
      * The {@link #run()} method disposes the resources.
      */
-    private final class Iterator implements Spliterator<Feature>, Consumer<Feature>, Runnable {
+    private final class Iterator implements Spliterator<AbstractFeature>, Consumer<AbstractFeature>, Runnable {
         /**
          * The main stream or a split iterator to close when the {@link #run()} method will be invoked.
          * This is initially the stream from which {@link #mainIterator} has been created. However if
@@ -347,31 +345,31 @@
          * <p>Only one iteration will be performed on those features, contrarily to the other side where we may
          * iterate over the same elements many times.</p>
          */
-        private final Spliterator<Feature> mainIterator;
+        private final Spliterator<AbstractFeature> mainIterator;
 
         /**
          * A feature fetched from the {@link #mainIterator}. The join operation will match this feature with
          * zero, one or more features from the other side. A {@code null} value means that this feature needs
          * to be retrieved with {@code mainIterator.tryAdvance(…)}.
          */
-        private Feature mainFeature;
+        private AbstractFeature mainFeature;
 
         /**
          * The stream over features in the other (usually right) side. A new stream will be created every time a new
          * feature from the main side is processed. For this reason, it should be the cheapest stream if possible.
          */
-        private Stream<Feature> filteredStream;
+        private Stream<AbstractFeature> filteredStream;
 
         /**
          * Iterator for the {@link #filteredStream}. A new iterator will be recreated every time a new feature
          * from the main side is processed.
          */
-        private Spliterator<Feature> filteredIterator;
+        private Spliterator<AbstractFeature> filteredIterator;
 
         /**
          * A feature fetched from the {@link #filteredIterator}, or {@code null} if none.
          */
-        private Feature filteredFeature;
+        private AbstractFeature filteredFeature;
 
         /**
          * Creates a new iterator. We do not use parallelized {@code mainStream} here because the {@code accept(…)}
@@ -380,7 +378,7 @@
          * so the {@link Stream} wrapping it can use parallelization.
          */
         Iterator() throws DataStoreException {
-            final Stream<Feature> mainStream = (swapSides ? right : left).features(false);
+            final Stream<AbstractFeature> mainStream = (swapSides ? right : left).features(false);
             mainCloseHandler = mainStream::close;
             mainIterator = mainStream.spliterator();
         }
@@ -388,7 +386,7 @@
         /**
          * Creates an iterator resulting from the call to {@link #trySplit()}.
          */
-        private Iterator(final Spliterator<Feature> it) {
+        private Iterator(final Spliterator<AbstractFeature> it) {
             mainIterator = it;
         }
 
@@ -398,8 +396,8 @@
          * Returns {@code null} if this iterator can not be partitioned.
          */
         @Override
-        public Spliterator<Feature> trySplit() {
-            final Spliterator<Feature> s = mainIterator.trySplit();
+        public Spliterator<AbstractFeature> trySplit() {
+            final Spliterator<AbstractFeature> s = mainIterator.trySplit();
             if (s == null) {
                 return null;
             }
@@ -451,7 +449,7 @@
          * This method is idempotent: it has no effect if the stream is already closed.
          */
         private void closeFilteredIterator() {
-            final Stream<Feature> stream = filteredStream;
+            final Stream<AbstractFeature> stream = filteredStream;
             filteredStream   = null;                // Cleared before call to close() in case of error.
             filteredIterator = null;
             filteredFeature  = null;                // Used as a sentinel value by this.forEachRemaining(…).
@@ -466,7 +464,7 @@
          * The filtering condition is determined by the current {@link #mainFeature}.
          */
         private void createFilteredIterator() {
-            final Expression<? super Feature, ?> expression1, expression2;
+            final Expression<? super AbstractFeature, ?> expression1, expression2;
             final FeatureSet filteredSet;
             if (swapSides) {
                 expression1 = condition.getOperand2();
@@ -478,10 +476,9 @@
                 filteredSet = right;
             }
             final Object mainValue = expression1.apply(mainFeature);
-            final Filter<? super Feature> filter;
+            final Filter<? super AbstractFeature> filter;
             if (mainValue != null) {
-                filter = factory.equal(expression2, factory.literal(mainValue),
-                            condition.isMatchingCase(), condition.getMatchAction());
+                filter = factory.equal(expression2, factory.literal(mainValue));
             } else {
                 filter = factory.isNull(expression2);
             }
@@ -499,13 +496,13 @@
          * Executes the given action on all remaining features in the {@code JoinFeatureSet}.
          */
         @Override
-        public void forEachRemaining(final Consumer<? super Feature> action) {
-            final Consumer<Feature> forFiltered = (final Feature feature) -> {
+        public void forEachRemaining(final Consumer<? super AbstractFeature> action) {
+            final Consumer<AbstractFeature> forFiltered = (final AbstractFeature feature) -> {
                 if (feature != null) {
                     action.accept(join(mainFeature, filteredFeature = feature));
                 }
             };
-            final Consumer<Feature> forMain = (final Feature feature) -> {
+            final Consumer<AbstractFeature> forMain = (final AbstractFeature feature) -> {
                 if (feature != null) {
                     mainFeature = feature;
                     createFilteredIterator();
@@ -527,7 +524,7 @@
          * Used by {@link #tryAdvance(Consumer)} implementation only.
          */
         @Override
-        public void accept(final Feature feature) {
+        public void accept(final AbstractFeature feature) {
             filteredFeature = feature;
         }
 
@@ -535,7 +532,7 @@
          * Executes the given action on the next feature in the {@code JoinFeatureSet}.
          */
         @Override
-        public boolean tryAdvance​(final Consumer<? super Feature> action) {
+        public boolean tryAdvance​(final Consumer<? super AbstractFeature> action) {
             for (;;) {
                 if (mainFeature == null) {
                     do if (!mainIterator.tryAdvance(this)) {
@@ -554,7 +551,7 @@
                         return true;
                     }
                 }
-                final Feature feature = mainFeature;
+                final AbstractFeature feature = mainFeature;
                 closeFilteredIterator();
                 if (none && isOuterJoin) {
                     action.accept(join(feature, null));
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 7249bbb..2ae3383 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
@@ -24,8 +24,8 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -42,24 +42,26 @@
     /**
      * The type specified at construction time and returned by {@link #getType()}.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType 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}.
+     * For all features in this collection, {@link AbstractFeature#getType()} shall be {@link #type}.
      */
-    private final Collection<Feature> features;
+    private final Collection<AbstractFeature> 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
+     * <code>{@linkplain AbstractFeature#getType()} == type</code> for all elements in the given collection
      * (this is not verified).
      *
      * @param parent     listeners of 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 StoreListeners parent, final FeatureType type, final Collection<Feature> features) {
+    public MemoryFeatureSet(final StoreListeners parent,
+                            final DefaultFeatureType type, final Collection<AbstractFeature> features)
+    {
         super(parent, false);
         ArgumentChecks.ensureNonNull("type",     type);
         ArgumentChecks.ensureNonNull("features", features);
@@ -73,7 +75,7 @@
      * @return a description of properties that are common to all features in this dataset.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -94,7 +96,7 @@
      * @return all features contained in this dataset.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) {
+    public Stream<AbstractFeature> features(final boolean parallel) {
         return parallel ? features.parallelStream() : features.stream();
     }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
index bee5a4d..a3d4b51 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
@@ -57,7 +57,6 @@
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.util.CollectionsExt;
-import org.apache.sis.internal.simple.SimpleDuration;
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.metadata.ModifiableMetadata;
 import org.apache.sis.metadata.iso.*;
@@ -96,8 +95,7 @@
 import static org.apache.sis.internal.util.StandardDateFormat.MILLISECONDS_PER_DAY;
 
 // Branch-dependent imports
-import org.opengis.temporal.Duration;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -1027,6 +1025,11 @@
         if (abbreviation != null && abbreviation.length() != 0) {
             if (format == null) {
                 format = MetadataSource.getProvided().lookup(Format.class, abbreviation);
+                /*
+                 * Additional step for converting deprecated "name" and "specification" into non-deprecated properties.
+                 * This step is not required on SIS branches that depend on development branches of GeoAPI 3.1 or 4.0.
+                 */
+                format = DefaultFormat.castOrCopy(format);
             } else {
                 addFormatName(abbreviation);
             }
@@ -1112,7 +1115,7 @@
      * </ul>
      *
      * If a date already exists for the given type, then the earliest date is retained (oldest date are discarded)
-     * except for {@link DateType#LAST_REVISION}, {@link DateType#LAST_UPDATE LAST_UPDATE} or any other date type
+     * except for {@code DateType.LAST_REVISION}, {@code DateType.LAST_UPDATE LAST_UPDATE} or any other date type
      * prefixed by {@code "LATE_"}, where only the latest date is kept.
      *
      * @param  date   the date to add, or {@code null} for no-operation..
@@ -1745,7 +1748,7 @@
             final DefaultCitation c = new DefaultCitation(notice);
             if (year != 0) {
                 final Date date = new Date(LocalDate.of(year, 1, 1).toEpochDay() * MILLISECONDS_PER_DAY);
-                c.setDates(Collections.singleton(new DefaultCitationDate(date, DateType.IN_FORCE)));
+                c.setDates(Collections.singleton(new DefaultCitationDate(date, DateType.valueOf("IN_FORCE"))));
             }
             if (i != 0) {
                 buffer.setLength(i);
@@ -1998,9 +2001,9 @@
      *         Note that ISO-19115 considers 0 as an invalid value. Consequently if 0, the feature is not added.
      * @return the name of the added feature (even if not added to the metadata), or {@code null} if none.
      *
-     * @see FeatureCatalogBuilder#define(FeatureType)
+     * @see FeatureCatalogBuilder#define(DefaultFeatureType)
      */
-    public final GenericName addFeatureType(final FeatureType type, final long occurrences) {
+    public final GenericName addFeatureType(final DefaultFeatureType type, final long occurrences) {
         if (type == null) {
             return null;
         }
@@ -2137,22 +2140,6 @@
     }
 
     /**
-     * Adds a temporal resolution in days.
-     * Storage location is:
-     *
-     * <ul>
-     *   <li>{@code metadata/identificationInfo/temporalResolution}</li>
-     * </ul>
-     *
-     * @param  duration  the resolution in days, or {@code NaN} for no-operation.
-     */
-    public final void addTemporalResolution(final double duration) {
-        if (!Double.isNaN(duration)) {
-            addIfNotPresent(identification().getTemporalResolutions(), new SimpleDuration(duration));
-        }
-    }
-
-    /**
      * Sets identification of grid data as point or cell.
      * Storage location is:
      *
@@ -2962,8 +2949,11 @@
             source.setSourceReferenceSystem(CollectionsExt.first(metadata.getReferenceSystemInfo()));
             for (final Identification id : metadata.getIdentificationInfo()) {
                 source.setSourceCitation(id.getCitation());
-                source.setSourceSpatialResolution(CollectionsExt.first(id.getSpatialResolutions()));
-                scope.setExtents(id.getExtents());
+                if (id instanceof AbstractIdentification) {
+                    final AbstractIdentification aid = (AbstractIdentification) id;
+                    source.setSourceSpatialResolution(CollectionsExt.first(aid.getSpatialResolutions()));
+                    scope.setExtents(aid.getExtents());
+                }
                 if (features != null && features.length != 0) {
                     /*
                      * Note: the same ScopeDescription may be shared by many Source instances
@@ -3174,7 +3164,7 @@
      *
      * <ul>
      *   <li>{@code metadata/metadataLinkage/linkage}
-     *     with {@code function} set to {@link OnLineFunction#COMPLETE_METADATA}</li>
+     *     with {@code function} set to {@code OnLineFunction.COMPLETE_METADATA}</li>
      * </ul>
      *
      * @param  link  URL to a more complete description of the metadata, or {@code null}.
@@ -3182,7 +3172,7 @@
     public final void addCompleteMetadata(final URI link) {
         if (link != null) {
             final DefaultOnlineResource ln = new DefaultOnlineResource(link);
-            ln.setFunction(OnLineFunction.COMPLETE_METADATA);
+            ln.setFunction(OnLineFunction.valueOf("COMPLETE_METADATA"));
             ln.setProtocol(link.getScheme());
             addIfNotPresent(metadata().getMetadataLinkages(), ln);
         }
@@ -3255,21 +3245,9 @@
                 for (ResponsibleParty r : c.getCitedResponsibleParties()) {
                     addIfNotPresent(citation.getCitedResponsibleParties(), r);
                 }
-                for (OnlineResource r : c.getOnlineResources()) {
-                    addIfNotPresent(citation.getOnlineResources(), r);
-                }
                 citation.getPresentationForms().addAll(c.getPresentationForms());
             }
             final DefaultDataIdentification identification = identification();
-            for (Extent e : info.getExtents()) {
-                addIfNotPresent(identification.getExtents(), e);
-            }
-            for (Resolution r : info.getSpatialResolutions()) {
-                addIfNotPresent(identification.getSpatialResolutions(), r);
-            }
-            for (Duration r : info.getTemporalResolutions()) {
-                addIfNotPresent(identification.getTemporalResolutions(), r);
-            }
             for (Format r : info.getResourceFormats()) {
                 addCompression(r.getFileDecompressionTechnique());
                 // Ignore format name (see Javadoc).
@@ -3277,8 +3255,17 @@
             for (Constraints r : info.getResourceConstraints()) {
                 addIfNotPresent(identification.getResourceConstraints(), r);
             }
-            identification.getTopicCategories().addAll(info.getTopicCategories());
-            identification.getSpatialRepresentationTypes().addAll(info.getSpatialRepresentationTypes());
+            if (info instanceof DataIdentification) {
+                final DataIdentification di = (DataIdentification) info;
+                for (Extent e : di.getExtents()) {
+                    addIfNotPresent(identification.getExtents(), e);
+                }
+                for (Resolution r : di.getSpatialResolutions()) {
+                    addIfNotPresent(identification.getSpatialResolutions(), r);
+                }
+                identification.getTopicCategories().addAll(di.getTopicCategories());
+                identification.getSpatialRepresentationTypes().addAll(di.getSpatialRepresentationTypes());
+            }
         }
         final DefaultMetadata metadata = metadata();
         for (ContentInformation info : component.getContentInfo()) {
@@ -3300,9 +3287,6 @@
                 addIfNotPresent(distribution().getDistributors(), r);
             }
         }
-        for (Lineage info : component.getResourceLineages()) {
-            addIfNotPresent(metadata.getResourceLineages(), info);
-        }
     }
 
     /**
@@ -3330,15 +3314,15 @@
         else if (source instanceof DataIdentification)          target = identification();
         else if (source instanceof Citation)                    target = citation();
         else if (source instanceof Series)                      target = series();
-        else if (source instanceof Responsibility)              target = responsibility();
-        else if (source instanceof Party)                       target = party();
+        else if (source instanceof DefaultResponsibleParty)     target = responsibility();
+        else if (source instanceof AbstractParty)               target = party();
         else if (source instanceof LegalConstraints)            target = constraints();
         else if (source instanceof Extent)                      target = extent();
         else if (source instanceof AcquisitionInformation)      target = acquisition();
         else if (source instanceof Platform)                    target = platform();
         else if (source instanceof FeatureCatalogueDescription) target = featureDescription();
         else if (source instanceof CoverageDescription)         target = coverageDescription();
-        else if (source instanceof AttributeGroup)              target = attributeGroup();
+        else if (source instanceof DefaultAttributeGroup)       target = attributeGroup();
         else if (source instanceof SampleDimension)             target = sampleDimension();
         else if (source instanceof GridSpatialRepresentation)   target = gridRepresentation();
         else if (source instanceof GCPCollection)               target = groundControlPoints();
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 eaeb355..ac16d00 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
@@ -53,7 +53,8 @@
 import org.apache.sis.util.Classes;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
 
 
 /**
@@ -191,7 +192,10 @@
         GeneralEnvelope bounds = null;
         if (metadata != null) {
             for (final Identification identification : metadata.getIdentificationInfo()) {
-                for (final Extent extent : identification.getExtents()) {
+                if (!(identification instanceof AbstractIdentification)) {
+                    continue;       // Following cast is specific to GeoAPI 3.0 branch.
+                }
+                for (final Extent extent : ((AbstractIdentification) identification).getExtents()) {
                     for (final GeographicExtent ge : extent.getGeographicElements()) {
                         if (ge instanceof GeographicBoundingBox) {
                             final GeneralEnvelope env = new GeneralEnvelope((GeographicBoundingBox) ge);
@@ -371,7 +375,7 @@
      */
     public static void copy(final FeatureSet source, final WritableFeatureSet target) throws DataStoreException {
         target.updateType(source.getType());
-        try (Stream<Feature> stream = source.features(false)) {
+        try (Stream<AbstractFeature> stream = source.features(false)) {
             target.add(stream.iterator());
         }
     }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
index 4b01e6a..f89e653 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
@@ -27,11 +27,11 @@
 import java.awt.image.RenderedImage;
 import java.awt.image.Raster;
 import org.opengis.util.GenericName;
-import org.opengis.coverage.CannotEvaluateException;
 import org.opengis.geometry.MismatchedDimensionException;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.DisjointExtentException;
+import org.apache.sis.coverage.CannotEvaluateException;
 import org.apache.sis.internal.coverage.j2d.DeferredProperty;
 import org.apache.sis.internal.coverage.j2d.TiledImage;
 import org.apache.sis.storage.DataStoreException;
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
index 09dc84c..d491a50 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
@@ -26,7 +26,6 @@
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
 import java.awt.image.RasterFormatException;
-import org.opengis.coverage.CannotEvaluateException;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridCoverage2D;
@@ -34,6 +33,7 @@
 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.CannotEvaluateException;
 import org.apache.sis.storage.AbstractGridCoverageResource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.RasterLoadingStrategy;
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/WritableResourceSupport.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/WritableResourceSupport.java
index 326a0ea..27b59aa 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/WritableResourceSupport.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/WritableResourceSupport.java
@@ -43,7 +43,7 @@
 import org.apache.sis.util.Localized;
 
 // Branch-dependent imports
-import org.opengis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.CannotEvaluateException;
 
 
 /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FeatureIterator.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FeatureIterator.java
index 73db26c..4f68826 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FeatureIterator.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FeatureIterator.java
@@ -27,9 +27,9 @@
 import org.apache.sis.util.collection.BackingStoreException;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -52,7 +52,7 @@
  * @since   0.7
  * @module
  */
-class FeatureIterator implements Spliterator<Feature> {
+class FeatureIterator implements Spliterator<AbstractFeature> {
     /**
      * Index of the column containing trajectory coordinates.
      * Columns before the trajectory are Moving Feature identifier {@code mfIdRef}, start time and end time.
@@ -94,12 +94,12 @@
     @SuppressWarnings({"unchecked", "rawtypes", "fallthrough"})
     FeatureIterator(final Store store) {
         this.store = store;
-        final Collection<? extends PropertyType> properties = store.featureType.getProperties(true);
+        final Collection<? extends AbstractIdentifiedType> properties = store.featureType.getProperties(true);
         converters    = new ObjectConverter[properties.size()];
         values        = new Object[converters.length];
         propertyNames = new String[converters.length];
         int i = -1;
-        for (final PropertyType p : properties) {
+        for (final AbstractIdentifiedType p : properties) {
             propertyNames[++i] = p.getName().tip().toString();
             /*
              * According Moving Features specification:
@@ -137,7 +137,7 @@
                      */
                 }
                 default: {
-                    c = ObjectConverters.find(String.class, ((AttributeType) p).getValueClass());
+                    c = ObjectConverters.find(String.class, ((DefaultAttributeType) p).getValueClass());
                     break;
                 }
             }
@@ -163,7 +163,7 @@
      * is not guaranteed to cover a strict prefix of the elements.
      */
     @Override
-    public Spliterator<Feature> trySplit() {
+    public Spliterator<AbstractFeature> trySplit() {
         if (splitCount == null) {
             splitCount = new AtomicInteger();
         }
@@ -177,7 +177,7 @@
      * Executes the given action only on the next feature, if any.
      */
     @Override
-    public boolean tryAdvance(final Consumer<? super Feature> action) {
+    public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
         try {
             return read(action, false);
         } catch (IOException | IllegalArgumentException | DateTimeException e) {
@@ -189,7 +189,7 @@
      * Executes the given action on all remaining features.
      */
     @Override
-    public void forEachRemaining(final Consumer<? super Feature> action) {
+    public void forEachRemaining(final Consumer<? super AbstractFeature> action) {
         try {
             read(action, true);
         } catch (IOException | IllegalArgumentException | DateTimeException e) {
@@ -215,12 +215,12 @@
      * @throws IllegalArgumentException if parsing of a number failed, or other error.
      * @throws DateTimeException if parsing of a date failed.
      */
-    private boolean read(final Consumer<? super Feature> action, final boolean all) throws IOException {
+    private boolean read(final Consumer<? super AbstractFeature> action, final boolean all) throws IOException {
         final FixedSizeList elements = new FixedSizeList(values);
         String line;
         while ((line = store.readLine()) != null) {
             Store.split(line, elements);
-            final Feature feature = store.featureType.newInstance();
+            final AbstractFeature feature = store.featureType.newInstance();
             int i, n = elements.size();
             for (i=0; i<n; i++) {
                 values[i] = converters[i].apply((String) values[i]);
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureBuilder.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureBuilder.java
index fca3055..e6d1170 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureBuilder.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureBuilder.java
@@ -31,8 +31,8 @@
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 
 // Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -114,7 +114,7 @@
 
     /**
      * Adds a time range.
-     * The minimal and maximal values will be used by {@link #storeTimeRange(String, String, Feature)}.
+     * The minimal and maximal values will be used by {@link #storeTimeRange(String, String, AbstractFeature)}.
      *
      * @param  startTime  beginning in milliseconds since Java epoch of the period when the property value is valid.
      * @param  endTime    end in milliseconds since Java epoch of the period when the the property value is valid.
@@ -149,7 +149,7 @@
      * @param  endTime    name of the property where to store the end time.
      * @param  dest       feature where to store the start time and end time.
      */
-    public final void storeTimeRange(final String startTime, final String endTime, final Feature dest) {
+    public final void storeTimeRange(final String startTime, final String endTime, final AbstractFeature dest) {
         if (tmin < tmax) {
             final Instant t = Instant.ofEpochMilli(tmin);
             dest.setPropertyValue(startTime, t);
@@ -166,7 +166,7 @@
      * @param  dest   attribute where to store the value.
      */
     @SuppressWarnings("unchecked")
-    public final <V> void storeAttribute(final int index, final Attribute<V> dest) {
+    public final <V> void storeAttribute(final int index, final AbstractAttribute<V> dest) {
         int n = count[index];
         final long[] times  = new long[n];
         final V[]    values = (V[]) Array.newInstance(dest.getType().getValueClass(), n);
@@ -196,7 +196,7 @@
      *                          source method name and logger name, then forward to a {@code WarningListener}.
      */
     public final <G> void storeGeometry(final String featureName, final int index, final int dimension,
-            final Geometries<G> factory, final Attribute<G> dest, final Consumer<LogRecord> warningListener)
+            final Geometries<G> factory, final AbstractAttribute<G> dest, final Consumer<LogRecord> warningListener)
     {
         int n = count[index];
         final Vector[] vectors = new Vector[n];
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java
index ad85cad..06cc1f2 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java
@@ -25,8 +25,8 @@
 import java.io.IOException;
 
 // Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -77,10 +77,10 @@
      * This method can only be invoked after {@link #readMoving(Consumer, boolean)} completion.
      * This method is ignored if the CSV file contains only static features.
      */
-    Feature[] createMovingFeatures() {
+    AbstractFeature[] createMovingFeatures() {
         int n = 0;
         final int np = values.length - TRAJECTORY_COLUMN;
-        final Feature[] features = new Feature[builders.size()];
+        final AbstractFeature[] features = new AbstractFeature[builders.size()];
         for (final Map.Entry<String,MovingFeatureBuilder> entry : builders.entrySet()) {
             features[n++] = createMovingFeature(entry.getKey(), entry.getValue(), np);
         }
@@ -95,18 +95,18 @@
      * @param  np           number of properties, ignoring the ones before the trajectory column.
      */
     @SuppressWarnings("unchecked")
-    private Feature createMovingFeature(final String featureName, final MovingFeatureBuilder mf, final int np) {
-        final Feature feature = store.featureType.newInstance();
+    private AbstractFeature createMovingFeature(final String featureName, final MovingFeatureBuilder mf, final int np) {
+        final AbstractFeature feature = store.featureType.newInstance();
         feature.setPropertyValue(propertyNames[0], featureName);
         mf.storeTimeRange(propertyNames[1], propertyNames[2], feature);
         int column = 0;
         if (store.hasTrajectories()) {
             mf.storeGeometry(featureName, column, store.spatialDimensionCount(), store.geometries,
-                    (Attribute) feature.getProperty(propertyNames[TRAJECTORY_COLUMN]), this);
+                    (AbstractAttribute) feature.getProperty(propertyNames[TRAJECTORY_COLUMN]), this);
             column++;
         }
         while (column < np) {
-            mf.storeAttribute(column, (Attribute<?>) feature.getProperty(propertyNames[TRAJECTORY_COLUMN + column]));
+            mf.storeAttribute(column, (AbstractAttribute<?>) feature.getProperty(propertyNames[TRAJECTORY_COLUMN + column]));
             column++;
         }
         return feature;
@@ -123,7 +123,7 @@
      * @throws IllegalArgumentException if parsing of a number failed, or other error.
      * @throws DateTimeException if parsing of a date failed.
      */
-    boolean readMoving(final Consumer<? super Feature> action, final boolean all) throws IOException {
+    boolean readMoving(final Consumer<? super AbstractFeature> action, final boolean all) throws IOException {
         final FixedSizeList elements = new FixedSizeList(values);
         final int np = values.length - TRAJECTORY_COLUMN;
         String line;
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
index adb49da..09fd319 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
@@ -77,10 +77,8 @@
 import org.apache.sis.measure.Units;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -171,7 +169,7 @@
      *
      * @see #parseFeatureType(List)
      */
-    final FeatureType featureType;
+    final DefaultFeatureType featureType;
 
     /**
      * {@code true} if {@link #featureType} contains a trajectory column.
@@ -221,7 +219,7 @@
      * All parsed moving features, or {@code null} if none or if not yet parsed. If {@link #dissociate}
      * is {@code false}, then this list will be created by {@link #features(boolean)} when first needed.
      */
-    private transient List<Feature> movingFeatures;
+    private transient List<AbstractFeature> movingFeatures;
 
     /**
      * Creates a new CSV store from the given file, URL or stream.
@@ -240,7 +238,7 @@
         geometries = Geometries.implementation(connector.getOption(OptionKey.GEOMETRY_LIBRARY));
         dissociate = FoliationRepresentation.FRAGMENTED.equals(connector.getOption(DataOptionKey.FOLIATION_REPRESENTATION));
         GeneralEnvelope envelope    = null;
-        FeatureType     featureType = null;
+        DefaultFeatureType featureType = null;
         Foliation       foliation   = null;
         try {
             final List<String> elements = new ArrayList<>();
@@ -509,10 +507,10 @@
      * @return the column metadata, or {@code null} if the given list does not contain enough elements.
      */
     @SuppressWarnings("rawtypes")               // "rawtypes" because of generic array creation.
-    private FeatureType parseFeatureType(final List<String> elements) throws DataStoreException {
-        AttributeType[] characteristics = null;
+    private DefaultFeatureType parseFeatureType(final List<String> elements) throws DataStoreException {
+        DefaultAttributeType[] characteristics = null;
         final int size = elements.size();
-        final List<PropertyType> properties = new ArrayList<>();
+        final List<AbstractIdentifiedType> properties = new ArrayList<>();
         for (int i=1; i<size; i++) {
             final String name = elements.get(i);
             Class<?> type = null;
@@ -567,7 +565,7 @@
                                 type = double[].class;
                             } else {
                                 type = geometries.polylineClass;
-                                characteristics = new AttributeType[] {MovingFeatureBuilder.TIME_AS_INSTANTS};
+                                characteristics = new DefaultAttributeType[] {MovingFeatureBuilder.TIME_AS_INSTANTS};
                             }
                             minOccurrence = 1;
                             maxOccurrence = 1;
@@ -580,15 +578,15 @@
         }
         final String name = IOUtilities.filenameWithoutExtension(super.getDisplayName());
         return new DefaultFeatureType(Collections.singletonMap(DefaultFeatureType.NAME_KEY, name),
-                false, null, properties.toArray(new PropertyType[properties.size()]));
+                false, null, properties.toArray(new AbstractIdentifiedType[properties.size()]));
     }
 
     /**
      * Creates a property type for the given name and type.
      * This is a helper method for {@link #parseFeatureType(List)}.
      */
-    private static PropertyType createProperty(final String name, final Class<?> type,
-            final int minOccurrence, final int maxOccurrence, final AttributeType<?>[] characteristics)
+    private static AbstractIdentifiedType createProperty(final String name, final Class<?> type,
+            final int minOccurrence, final int maxOccurrence, final DefaultAttributeType<?>[] characteristics)
     {
         return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name),
                 type, minOccurrence, maxOccurrence, null, characteristics);
@@ -678,7 +676,7 @@
      * @return type of features in the CSV file.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return featureType;
     }
 
@@ -693,7 +691,7 @@
      * @todo If sequential order, publish Feature as soon as identifier changed.
      */
     @Override
-    public final synchronized Stream<Feature> features(final boolean parallel) throws DataStoreException {
+    public final synchronized Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
         /*
          * If the user asks for one feature instance per line, then we can return a FeatureIter instance directly.
          * Since each feature is fully constructed from a single line and each line are read atomically, we can
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
index 5dcda27..a6b9feb 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
@@ -175,7 +175,7 @@
             builder.addFormatName(formatName);
             listeners.warning(e);
         }
-        builder.addResourceScope(ScopeCode.COVERAGE, null);
+        builder.addResourceScope(ScopeCode.valueOf("COVERAGE"), null);
         builder.addEncoding(encoding, MetadataBuilder.Scope.METADATA);
         builder.addSpatialRepresentation(null, gridGeometry, true);
         try {
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
index 6f10d6c..c79cf3f 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
@@ -42,7 +42,7 @@
 import org.apache.sis.util.StringBuilders;
 
 // Branch-dependent imports
-import org.opengis.coverage.grid.SequenceType;
+import org.apache.sis.image.SequenceType;
 
 
 /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
index 22c788d..1315e82 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
@@ -267,7 +267,7 @@
     public synchronized Metadata getMetadata() {
         if (metadata == null) {
             final MetadataBuilder mb = new MetadataBuilder();
-            mb.addResourceScope(ScopeCode.COLLECTION, Resources.formatInternational(Resources.Keys.DirectoryContent_1, getDisplayName()));
+            mb.addResourceScope(ScopeCode.valueOf("COLLECTION"), Resources.formatInternational(Resources.Keys.DirectoryContent_1, getDisplayName()));
             mb.addLanguage(locale,   MetadataBuilder.Scope.RESOURCE);
             mb.addEncoding(encoding, MetadataBuilder.Scope.RESOURCE);
             String name = null;
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/StoreProvider.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/StoreProvider.java
index f390622..61bf90a 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/StoreProvider.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/StoreProvider.java
@@ -50,6 +50,9 @@
 import org.apache.sis.internal.storage.StoreUtilities;
 import org.apache.sis.setup.OptionKey;
 
+// Branch-dependent imports
+import org.apache.sis.parameter.DefaultParameterDescriptor;
+
 
 /**
  * The provider of {@link Store} instances. This provider is intentionally registered with lowest priority
@@ -113,7 +116,7 @@
      * Creates a parameter descriptor equals to the given one except for the remarks which are set to the given value.
      */
     private static <T> ParameterDescriptor<T> annotate(ParameterBuilder builder, ParameterDescriptor<T> e, InternationalString remark) {
-        return builder.addName(e.getName()).setDescription(e.getDescription()).setRemarks(remark).create(e.getValueClass(), null);
+        return builder.addName(e.getName()).setDescription(((DefaultParameterDescriptor) e).getDescription()).setRemarks(remark).create(e.getValueClass(), null);
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
index ccbc947..fd15f2c 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
@@ -533,7 +533,7 @@
                 }
             }
             builder.addFormatName(format);                          // Does nothing if `format` is null.
-            builder.addResourceScope(ScopeCode.COVERAGE, null);
+            builder.addResourceScope(ScopeCode.valueOf("COVERAGE"), null);
             builder.addSpatialRepresentation(null, getGridGeometry(MAIN_IMAGE), true);
             if (gridGeometry.isDefined(GridGeometry.ENVELOPE)) {
                 builder.addExtent(gridGeometry.getEnvelope());
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/GeographicEnvelope.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/GeographicEnvelope.java
index 256dba3..8e306d5 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/GeographicEnvelope.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/GeographicEnvelope.java
@@ -23,6 +23,9 @@
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.extent.GeographicExtent;
+import org.opengis.metadata.extent.TemporalExtent;
+import org.opengis.metadata.extent.VerticalExtent;
+import org.opengis.util.InternationalString;
 import org.apache.sis.referencing.CommonCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
@@ -112,6 +115,17 @@
     }
 
     /**
+     * Returns the spatial and temporal extent for the referring object.
+     * The default implementation unconditionally returns {@code null}.
+     *
+     * @return the spatial and temporal extent, or {@code null} in none.
+     */
+    @Override
+    public InternationalString getDescription() {
+        return null;
+    }
+
+    /**
      * Provides geographic component of the extent of the referring object.
      * The default implementation returns a singleton containing only this
      * geographic bounding box.
@@ -124,6 +138,28 @@
     }
 
     /**
+     * Provides temporal component of the extent of the referring object.
+     * The default implementation unconditionally returns an empty set.
+     *
+     * @return the temporal extent, or an empty set if none.
+     */
+    @Override
+    public Collection<? extends TemporalExtent> getTemporalElements() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * Provides vertical component of the extent of the referring object.
+     * The default implementation unconditionally returns an empty set.
+     *
+     * @return the vertical extent, or an empty set if none.
+     */
+    @Override
+    public Collection<? extends VerticalExtent> getVerticalElements() {
+        return Collections.emptySet();
+    }
+
+    /**
      * Indication of whether the bounding box encompasses an area covered by the data
      * (<cite>inclusion</cite>) or an area where data is not present (<cite>exclusion</cite>).
      * The default implementation unconditionally returns {@link Boolean#TRUE}.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/AbstractFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/AbstractFeatureSet.java
index be5fd48..3ad196f 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/AbstractFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/AbstractFeatureSet.java
@@ -24,7 +24,7 @@
 import org.apache.sis.internal.storage.MetadataBuilder;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -82,7 +82,7 @@
      */
     @Override
     public Optional<GenericName> getIdentifier() throws DataStoreException {
-        final FeatureType type = getType();
+        final DefaultFeatureType type = getType();
         return (type != null) ? Optional.of(type.getName()) : Optional.empty();
     }
 
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/Aggregate.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/Aggregate.java
index c63da03..f4bc884 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/Aggregate.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/Aggregate.java
@@ -48,10 +48,10 @@
  * {@link org.apache.sis.metadata.iso.DefaultMetadata#getMetadataScopes() metadataScope} /
  * {@link org.apache.sis.metadata.iso.DefaultMetadataScope#getResourceScope() resourceScope} sets to
  * {@link org.opengis.metadata.maintenance.ScopeCode#SERIES} or
- * {@link org.opengis.metadata.maintenance.ScopeCode#INITIATIVE} if applicable.
+ * {@code ScopeCode.INITIATIVE} if applicable.
  * If not too expensive to compute, the names of all components should be listed as
  * {@linkplain org.apache.sis.metadata.iso.identification.AbstractIdentification#getAssociatedResources()
- * associated resources} with an {@link org.opengis.metadata.identification.AssociationType#IS_COMPOSED_OF} relation.
+ * associated resources} with an {@code AssociationType.IS_COMPOSED_OF} relation.
  *
  * @author  Johann Sorel (Geomatys)
  * @version 1.0
@@ -68,7 +68,7 @@
      * <blockquote><code><b>this</b>.metadata</code> /
      * {@link org.apache.sis.metadata.iso.DefaultMetadata#getIdentificationInfo() identificationInfo} /
      * {@link org.apache.sis.metadata.iso.identification.AbstractIdentification#getAssociatedResources() associatedResource}
-     * with {@link org.opengis.metadata.identification.AssociationType#IS_COMPOSED_OF}</blockquote>
+     * with {@code AssociationType.IS_COMPOSED_OF}</blockquote>
      *
      * The name of each child resource in the returned collection is given by the following metadata element:
      *
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 5b8f2e8..fdbb820 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
@@ -40,6 +40,9 @@
 import org.apache.sis.storage.event.StoreListener;
 import org.apache.sis.storage.event.StoreListeners;
 
+// Branch-specific imports
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Manages a series of features, coverages or sensor data.
@@ -340,12 +343,12 @@
                 }
             }
             if (citation != null) {
-                Identifier first = null;
+                ReferenceIdentifier first = null;
                 for (final Identifier c : citation.getIdentifiers()) {
                     if (c instanceof GenericName) {
                         return Optional.of((GenericName) c);
-                    } else if (first == null) {
-                        first = c;
+                    } else if (first == null && c instanceof ReferenceIdentifier) {
+                        first = (ReferenceIdentifier) c;
                     }
                 }
                 if (first != null) {
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureNaming.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureNaming.java
index 82b8f23..b601e0d 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureNaming.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureNaming.java
@@ -31,8 +31,8 @@
 
 /**
  * Helper class for mapping {@link GenericName} instances and their shortened names to features.
- * The features are typically represented by instances of {@link org.opengis.feature.FeatureType}
- * or {@link org.opengis.coverage.Coverage} (sometime seen as a kind of features), but this class
+ * The features are typically represented by instances of {@code FeatureType}
+ * or {@code Coverage} (sometime seen as a kind of features), but this class
  * actually puts no restriction on the kind of object associated to {@code GenericName}s;
  * {@link DataStore} implementations are free to choose their internal object.
  * Those objects can be stored and fetched using the {@code String} representation of their name
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
index d834408..ab541b9 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
@@ -43,16 +43,14 @@
 import org.apache.sis.util.resources.Vocabulary;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.SortBy;
-import org.opengis.filter.SortProperty;
-import org.opengis.filter.InvalidFilterValueException;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.internal.geoapi.filter.Literal;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
+import org.apache.sis.internal.geoapi.filter.SortBy;
+import org.apache.sis.internal.geoapi.filter.SortProperty;
 
 
 /**
@@ -109,7 +107,7 @@
      * @see #getSelection()
      * @see #setSelection(Filter)
      */
-    private Filter<? super Feature> selection;
+    private Filter<? super AbstractFeature> selection;
 
     /**
      * The number of feature instances to skip from the beginning.
@@ -137,7 +135,7 @@
      * @see #getSortBy()
      * @see #setSortBy(SortBy)
      */
-    private SortBy<Feature> sortBy;
+    private SortBy<AbstractFeature> sortBy;
 
     /**
      * Hint used by resources to optimize returned features.
@@ -168,7 +166,7 @@
     public void setProjection(final String... properties) {
         NamedExpression[] wrappers = null;
         if (properties != null) {
-            final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+            final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
             wrappers = new NamedExpression[properties.length];
             for (int i=0; i<wrappers.length; i++) {
                 final String p = properties[i];
@@ -188,12 +186,12 @@
      * @throws IllegalArgumentException if a property is duplicated.
      */
     @SafeVarargs
-    public final void setProjection(final Expression<? super Feature, ?>... properties) {
+    public final void setProjection(final Expression<? super AbstractFeature, ?>... properties) {
         NamedExpression[] wrappers = null;
         if (properties != null) {
             wrappers = new NamedExpression[properties.length];
             for (int i=0; i<wrappers.length; i++) {
-                final Expression<? super Feature, ?> e = properties[i];
+                final Expression<? super AbstractFeature, ?> e = properties[i];
                 ArgumentChecks.ensureNonNullElement("properties", i, e);
                 wrappers[i] = new NamedExpression(e);
             }
@@ -254,9 +252,9 @@
      */
     @Override
     public void setSelection(final Envelope domain) {
-        Filter<? super Feature> filter = null;
+        Filter<? super AbstractFeature> filter = null;
         if (domain != null) {
-            final FilterFactory<Feature,Object,?> ff = DefaultFilterFactory.forFeatures();
+            final DefaultFilterFactory<AbstractFeature,Object,?> ff = DefaultFilterFactory.forFeatures();
             filter = ff.bbox(ff.property(AttributeConvention.GEOMETRY), domain);
         }
         setSelection(filter);
@@ -269,7 +267,7 @@
      *
      * @param  selection  the filter, or {@code null} if none.
      */
-    public void setSelection(final Filter<? super Feature> selection) {
+    public void setSelection(final Filter<? super AbstractFeature> selection) {
         this.selection = selection;
     }
 
@@ -280,7 +278,7 @@
      *
      * @return the filter, or {@code null} if none.
      */
-    public Filter<? super Feature> getSelection() {
+    public Filter<? super AbstractFeature> getSelection() {
         return selection;
     }
 
@@ -343,15 +341,17 @@
 
     /**
      * Sets the expressions to use for sorting the feature instances.
-     * {@code SortBy} objects are used to order the {@link Feature} instances returned by the {@link FeatureSet}.
+     * {@code SortBy} objects are used to order the {@code Feature} instances returned by the {@link FeatureSet}.
      * {@code SortBy} clauses are applied in declaration order, like SQL.
      *
      * @param  properties  expressions to use for sorting the feature instances,
      *                     or {@code null} or an empty array if none.
+     *
+     * @todo Not yet in public API. Pending publication of {@link SortProperty} interface.
      */
     @SafeVarargs
-    public final void setSortBy(final SortProperty<Feature>... properties) {
-        SortBy<Feature> sortBy = null;
+    final void setSortBy(final SortProperty<AbstractFeature>... properties) {
+        SortBy<AbstractFeature> sortBy = null;
         if (properties != null) {
             sortBy = SortByComparator.create(properties);
         }
@@ -360,11 +360,13 @@
 
     /**
      * Sets the expressions to use for sorting the feature instances.
-     * {@code SortBy} objects are used to order the {@link Feature} instances returned by the {@link FeatureSet}.
+     * {@code SortBy} objects are used to order the {@code Feature} instances returned by the {@link FeatureSet}.
      *
      * @param  sortBy  expressions to use for sorting the feature instances, or {@code null} if none.
+     *
+     * @todo Not yet in public API. Pending publication of {@link SortProperty} interface.
      */
-    public void setSortBy(final SortBy<Feature> sortBy) {
+    final void setSortBy(final SortBy<AbstractFeature> sortBy) {
         this.sortBy = sortBy;
     }
 
@@ -373,8 +375,10 @@
      * This is the value specified in the last call to {@link #setSortBy(SortBy)}.
      *
      * @return expressions to use for sorting the feature instances, or {@code null} if none.
+     *
+     * @todo Not yet in public API. Pending publication of {@link SortProperty} interface.
      */
-    public SortBy<Feature> getSortBy() {
+    final SortBy<AbstractFeature> getSortBy() {
         return sortBy;
     }
 
@@ -416,7 +420,7 @@
          * The literal, value reference or more complex expression to be retrieved by a {@code Query}.
          * Never {@code null}.
          */
-        public final Expression<? super Feature, ?> expression;
+        public final Expression<? super AbstractFeature, ?> expression;
 
         /**
          * The name to assign to the expression result, or {@code null} if unspecified.
@@ -428,7 +432,7 @@
          *
          * @param expression  the literal, value reference or expression to be retrieved by a {@code Query}.
          */
-        public NamedExpression(final Expression<? super Feature, ?> expression) {
+        public NamedExpression(final Expression<? super AbstractFeature, ?> expression) {
             ArgumentChecks.ensureNonNull("expression", expression);
             this.expression = expression;
             this.alias = null;
@@ -440,7 +444,7 @@
          * @param expression  the literal, value reference or expression to be retrieved by a {@code Query}.
          * @param alias       the name to assign to the expression result, or {@code null} if unspecified.
          */
-        public NamedExpression(final Expression<? super Feature, ?> expression, final GenericName alias) {
+        public NamedExpression(final Expression<? super AbstractFeature, ?> expression, final GenericName alias) {
             ArgumentChecks.ensureNonNull("expression", expression);
             this.expression = expression;
             this.alias = alias;
@@ -453,7 +457,7 @@
          * @param expression  the literal, value reference or expression to be retrieved by a {@code Query}.
          * @param alias       the name to assign to the expression result, or {@code null} if unspecified.
          */
-        public NamedExpression(final Expression<? super Feature, ?> expression, final String alias) {
+        public NamedExpression(final Expression<? super AbstractFeature, ?> expression, final String alias) {
             ArgumentChecks.ensureNonNull("expression", expression);
             this.expression = expression;
             this.alias = (alias != null) ? Names.createLocalName(null, null, alias) : null;
@@ -579,10 +583,10 @@
      * @return type resulting from expressions evaluation (never null).
      * @throws IllegalArgumentException if this method can operate only on some feature types
      *         and the given type is not one of them.
-     * @throws InvalidFilterValueException if this method can not determine the result type of an expression
+     * @throws IllegalArgumentException if this method can not determine the result type of an expression
      *         in this query. It may be because that expression is backed by an unsupported implementation.
      */
-    final FeatureType expectedType(final FeatureType valueType) {
+    final DefaultFeatureType expectedType(final DefaultFeatureType valueType) {
         if (projection == null) {
             return valueType;           // All columns included: result is of the same type.
         }
@@ -599,7 +603,7 @@
             final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(expression);
             final PropertyTypeBuilder resultType;
             if (fex == null || (resultType = fex.expectedType(valueType, ftb)) == null) {
-                throw new InvalidFilterValueException(Resources.format(Resources.Keys.InvalidExpression_2,
+                throw new IllegalArgumentException(Resources.format(Resources.Keys.InvalidExpression_2,
                             expression.getFunctionName().toInternationalString(), column));
             }
             if (name == null) {
@@ -725,7 +729,7 @@
         }
         if (sortBy != null) {
             String separator = " ORDER BY ";
-            for (final SortProperty<Feature> p : sortBy.getSortProperties()) {
+            for (final SortProperty<AbstractFeature> p : sortBy.getSortProperties()) {
                 sb.append(separator);
                 separator = ", ";
                 sb.append(p.getValueReference().getXPath()).append(' ').append(p.getSortOrder());
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSet.java
index 286739b..efc2294 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSet.java
@@ -20,8 +20,8 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -69,14 +69,14 @@
      * @return description of common properties (never {@code null}).
      * @throws DataStoreException if an error occurred while reading definitions from the underlying data store.
      */
-    FeatureType getType() throws DataStoreException;
+    DefaultFeatureType getType() throws DataStoreException;
 
     /**
      * Requests a subset of features and/or feature properties from this resource.
      * The filtering can be applied in two domains:
      *
      * <ul>
-     *   <li>The returned {@code FeatureSet} may contain a smaller number of {@link Feature} instances.</li>
+     *   <li>The returned {@code FeatureSet} may contain a smaller number of {@code Feature} instances.</li>
      *   <li>In each {@code Feature} instance of the returned set, the number of
      *       {@linkplain org.apache.sis.feature.DefaultFeatureType#getProperty properties} may be smaller.</li>
      * </ul>
@@ -149,5 +149,5 @@
      * @return all features contained in this dataset.
      * @throws DataStoreException if an error occurred while creating the stream.
      */
-    Stream<Feature> features(boolean parallel) throws DataStoreException;
+    Stream<AbstractFeature> features(boolean parallel) throws DataStoreException;
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
index 4affc96..8edc78f 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
@@ -22,11 +22,11 @@
 import org.apache.sis.internal.storage.Resources;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.SortBy;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.internal.geoapi.filter.SortBy;
 
 
 /**
@@ -56,7 +56,7 @@
      * The type of features in this set. May or may not be the same as {@link #source}.
      * This is computed when first needed.
      */
-    private FeatureType resultType;
+    private DefaultFeatureType resultType;
 
     /**
      * Creates a new set of features by filtering the given set using the given query.
@@ -72,9 +72,9 @@
      * Returns a description of properties that are common to all features in this dataset.
      */
     @Override
-    public synchronized FeatureType getType() throws DataStoreException {
+    public synchronized DefaultFeatureType getType() throws DataStoreException {
         if (resultType == null) {
-            final FeatureType type = source.getType();
+            final DefaultFeatureType type = source.getType();
             try {
                 resultType = query.expectedType(type);
             } catch (IllegalArgumentException e) {
@@ -89,19 +89,19 @@
      * Returns a stream of all features contained in this dataset.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) throws DataStoreException {
-        Stream<Feature> stream = source.features(parallel);
+    public Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
+        Stream<AbstractFeature> stream = source.features(parallel);
         /*
          * Apply filter.
          */
-        final Filter<? super Feature> selection = query.getSelection();
+        final Filter<? super AbstractFeature> selection = query.getSelection();
         if (selection != null && !selection.equals(Filter.include())) {
             stream = stream.filter(selection);
         }
         /*
          * Apply sorting.
          */
-        final SortBy<Feature> sortBy = query.getSortBy();
+        final SortBy<AbstractFeature> sortBy = query.getSortBy();
         if (sortBy != null) {
             stream = stream.sorted(sortBy);
         }
@@ -126,14 +126,14 @@
         final FeatureQuery.NamedExpression[] projection = query.getProjection();
         if (projection != null) {
             @SuppressWarnings({"unchecked", "rawtypes"})
-            final Expression<? super Feature, ?>[] expressions = new Expression[projection.length];
+            final Expression<? super AbstractFeature, ?>[] expressions = new Expression[projection.length];
             for (int i=0; i<expressions.length; i++) {
                 expressions[i] = projection[i].expression;
             }
-            final FeatureType type = getType();
+            final DefaultFeatureType type = getType();
             final String[] names = FeatureUtilities.getNames(type.getProperties(false));
             stream = stream.map(t -> {
-                final Feature f = type.newInstance();
+                final AbstractFeature f = type.newInstance();
                 for (int i=0; i < expressions.length; i++) {
                     f.setPropertyValue(names[i], expressions[i].apply(t));
                 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java
index 267c21a..c2571ed 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java
@@ -23,7 +23,7 @@
 
 /**
  * Thrown when a store can not write the given feature because its type is not one of the supported types.
- * The {@link org.opengis.feature.FeatureType} is given by {@link org.opengis.feature.Feature#getType()},
+ * The {@code FeatureType} is given by {@code Feature.getType()},
  * and the type expected by the data store is given by {@link FeatureSet#getType()}. Those two values must
  * match, except when the type of the feature set is {@linkplain WritableFeatureSet#updateType updated}.
  *
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
index 4d58d2f..2a72db7 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
@@ -18,9 +18,6 @@
 
 import org.opengis.geometry.Envelope;
 
-// Branch-dependent imports
-import org.opengis.filter.QueryExpression;
-
 
 /**
  * Definition of filtering to apply for fetching a resource subset.
@@ -53,7 +50,7 @@
  * @since 0.8
  * @module
  */
-public abstract class Query implements QueryExpression {
+public abstract class Query {
     /**
      * Creates a new, initially empty, query.
      */
@@ -62,7 +59,7 @@
 
     /**
      * Sets the approximate area of feature instances or pixels to include in the subset.
-     * For feature set, the domain is materialized by a {@link org.opengis.filter.Filter}.
+     * For feature set, the domain is materialized by a {@link org.apache.sis.filter.Filter}.
      * For grid coverage resource, the given envelope specifies the coverage domain.
      *
      * <p>The given envelope is approximate.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/RasterLoadingStrategy.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/RasterLoadingStrategy.java
index 3c1a16e..92045e5 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/RasterLoadingStrategy.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/RasterLoadingStrategy.java
@@ -19,10 +19,10 @@
 import java.lang.ref.SoftReference;
 import java.awt.image.RenderedImage;
 import java.awt.image.ImagingOpException;
-import org.opengis.coverage.CannotEvaluateException;
 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.CannotEvaluateException;
 
 
 /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java
index 637812a..4302185 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java
@@ -111,7 +111,7 @@
      *       {@link org.opengis.metadata.maintenance.ScopeCode#DATASET} if the resource is a {@link DataSet}, or
      *       {@link org.opengis.metadata.maintenance.ScopeCode#SERVICE} if the resource is a web service, or
      *       {@link org.opengis.metadata.maintenance.ScopeCode#SERIES} or
-     *       {@link org.opengis.metadata.maintenance.ScopeCode#INITIATIVE}
+     *       {@code ScopeCode.INITIATIVE}
      *       if the resource is an {@link Aggregate} other than a transfer aggregate.</li>
      *   <li>{@code metadata} /
      *       {@link org.apache.sis.metadata.iso.DefaultMetadata#getContentInfo() contentInfo} /
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/WritableFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/WritableFeatureSet.java
index 29cc254..12b7458 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/WritableFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/WritableFeatureSet.java
@@ -22,8 +22,8 @@
 import java.util.function.UnaryOperator;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -51,12 +51,12 @@
      * @throws IllegalFeatureTypeException if the given type is not compatible with the types supported by the store.
      * @throws DataStoreException if another error occurred while changing the feature type.
      */
-    void updateType(FeatureType newType) throws DataStoreException;
+    void updateType(DefaultFeatureType newType) throws DataStoreException;
 
     /**
      * Inserts new feature instances in this {@code FeatureSet}.
      * Any feature already present in this {@link FeatureSet} will remain unmodified.
-     * If a {@linkplain Feature#getProperty feature property} is used as unique identifier, then:
+     * If a {@linkplain AbstractFeature#getProperty feature property} is used as unique identifier, then:
      *
      * <ul>
      *   <li>If a given feature assigns to that property a value already in use, an exception will be thrown.</li>
@@ -76,7 +76,7 @@
      * @throws IllegalFeatureTypeException if a feature given by the iterator is not of the type expected by this {@code FeatureSet}.
      * @throws DataStoreException if another error occurred while storing new features.
      */
-    void add(Iterator<? extends Feature> features) throws DataStoreException;
+    void add(Iterator<? extends AbstractFeature> features) throws DataStoreException;
 
     /**
      * Removes all feature instances from this {@code FeatureSet} which matches the given predicate.
@@ -85,25 +85,25 @@
      * @return {@code true} if any elements were removed.
      * @throws DataStoreException if an error occurred while removing features.
      */
-    boolean removeIf(Predicate<? super Feature> filter) throws DataStoreException;
+    boolean removeIf(Predicate<? super AbstractFeature> filter) throws DataStoreException;
 
     /**
      * Updates all feature instances from this {@code FeatureSet} which match the given predicate.
-     * For each {@link Feature} instance matching the given {@link Predicate},
+     * For each {@code Feature} instance matching the given {@link Predicate},
      * the <code>{@linkplain UnaryOperator#apply UnaryOperator.apply(Feature)}</code> method will be invoked.
      * {@code UnaryOperator}s are free to modify the given {@code Feature} <i>in-place</i>
      * or to return a different feature instance. Two behaviors are possible:
      *
      * <ul>
-     *   <li>If the operator returns a non-null {@link Feature}, then the modified feature is stored
+     *   <li>If the operator returns a non-null {@code Feature}, then the modified feature is stored
      *       in replacement of the previous feature (not necessarily at the same location).</li>
      *   <li>If the operator returns {@code null}, then the feature will be removed from the {@code FeatureSet}.</li>
      * </ul>
      *
      * @param  filter   a predicate which returns {@code true} for feature instances to be updated.
-     * @param  updater  operation called for each matching {@link Feature} instance.
+     * @param  updater  operation called for each matching {@code Feature} instance.
      * @throws IllegalFeatureTypeException if a feature given by the operator is not of the type expected by this {@code FeatureSet}.
      * @throws DataStoreException if another error occurred while replacing features.
      */
-    void replaceIf(Predicate<? super Feature> filter, UnaryOperator<Feature> updater) throws DataStoreException;
+    void replaceIf(Predicate<? super AbstractFeature> filter, UnaryOperator<AbstractFeature> updater) throws DataStoreException;
 }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ConcatenatedFeatureSetTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ConcatenatedFeatureSetTest.java
index 7841638..f1786d4 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ConcatenatedFeatureSetTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ConcatenatedFeatureSetTest.java
@@ -33,8 +33,9 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.metadata.iso.DefaultMetadata;
 
 
 /**
@@ -58,7 +59,7 @@
         builder.addAttribute(String.class).setName("name");
         builder.addAttribute(Integer.class).setName("population");
 
-        final FeatureType ft = builder.build();
+        final DefaultFeatureType ft = builder.build();
         final FeatureSet cfs = ConcatenatedFeatureSet.create(
                 new MemoryFeatureSet(null, ft, Arrays.asList(ft.newInstance(), ft.newInstance())),
                 new MemoryFeatureSet(null, ft, Arrays.asList(ft.newInstance())));
@@ -69,7 +70,7 @@
         final Metadata md = cfs.getMetadata();
         assertNotNull("getMetadata()", md);
         assertContentInfoEquals("City", 3, (FeatureCatalogueDescription) getSingleton(md.getContentInfo()));
-        final Lineage lineage = getSingleton(md.getResourceLineages());
+        final Lineage lineage = getSingleton(((DefaultMetadata) md).getResourceLineages());
         assertFeatureSourceEquals("City", new String[] {"City"}, getSingleton(lineage.getSources()));
     }
 
@@ -89,32 +90,32 @@
         builder.addAttribute(Integer.class).setName("value");
         builder.setName("parent");
 
-        final FeatureType superType = builder.build();
+        final DefaultFeatureType superType = builder.build();
 
         builder.clear();
         builder.setSuperTypes(superType);
         builder.addAttribute(String.class).setName("label");
 
-        final FeatureType t1 = builder.setName("t1").build();
-        final FeatureType t2 = builder.setName("t2").build();
+        final DefaultFeatureType t1 = builder.setName("t1").build();
+        final DefaultFeatureType t2 = builder.setName("t2").build();
 
         // Populate a feature set for first type.
-        final Feature t1f1 = t1.newInstance();
+        final AbstractFeature t1f1 = t1.newInstance();
         t1f1.setPropertyValue("value", 2);
         t1f1.setPropertyValue("label", "first-first");
 
-        final Feature t1f2 = t1.newInstance();
+        final AbstractFeature t1f2 = t1.newInstance();
         t1f2.setPropertyValue("value", 3);
         t1f2.setPropertyValue("label", "first-second");
 
         final FeatureSet t1fs = new MemoryFeatureSet(null, t1, Arrays.asList(t1f1, t1f2));
 
         // Populate a feature set for second type.
-        final Feature t2f1 = t2.newInstance();
+        final AbstractFeature t2f1 = t2.newInstance();
         t2f1.setPropertyValue("value", 3);
         t2f1.setPropertyValue("label", "second-first");
 
-        final Feature t2f2 = t2.newInstance();
+        final AbstractFeature t2f2 = t2.newInstance();
         t2f2.setPropertyValue("value", 4);
         t2f2.setPropertyValue("label", "second-second");
 
@@ -151,9 +152,9 @@
     public void noCommonType() {
         final FeatureTypeBuilder builder = new FeatureTypeBuilder();
         builder.setName("super");
-        final FeatureType mockSuperType = builder.build();
-        final FeatureType firstType  = builder.setSuperTypes(mockSuperType).setName("first").build();
-        final FeatureType secondType = builder.clear().setName("second").build();
+        final DefaultFeatureType mockSuperType = builder.build();
+        final DefaultFeatureType firstType  = builder.setSuperTypes(mockSuperType).setName("first").build();
+        final DefaultFeatureType secondType = builder.clear().setName("second").build();
         final FeatureSet fs1 = new MemoryFeatureSet(null, firstType,  Collections.emptyList());
         final FeatureSet fs2 = new MemoryFeatureSet(null, secondType, Collections.emptyList());
         try {
@@ -180,7 +181,7 @@
             // This is the expected exception.
         }
         final FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("mock");
-        final FeatureType mockType = builder.build();
+        final DefaultFeatureType mockType = builder.build();
         final FeatureSet fs1 = new MemoryFeatureSet(null, mockType, Collections.emptyList());
         final FeatureSet set = ConcatenatedFeatureSet.create(fs1);
         assertSame("A concatenation has been created from a single type.", fs1, set);
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/JoinFeatureSetTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/JoinFeatureSetTest.java
index 7a38075..fa57594 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/JoinFeatureSetTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/JoinFeatureSetTest.java
@@ -34,11 +34,11 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.MatchAction;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.internal.geoapi.filter.BinaryComparisonOperator;
 
 
 /**
@@ -70,7 +70,7 @@
         builder.addAttribute( String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY);
         builder.addAttribute( String.class).setName("myNameSpace", "att1");
         builder.addAttribute(Integer.class).setName("myNameSpace", "att2");
-        final FeatureType type1 = builder.build();
+        final DefaultFeatureType type1 = builder.build();
         featureSet1 = new MemoryFeatureSet(null, type1, Arrays.asList(
                 newFeature1(type1, "fid_1_0", "str1",   1),
                 newFeature1(type1, "fid_1_1", "str2",   2),
@@ -82,7 +82,7 @@
         builder.addAttribute( String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY);
         builder.addAttribute(Integer.class).setName("otherNameSpace", "att3");
         builder.addAttribute( Double.class).setName("otherNameSpace", "att4");
-        final FeatureType type2 = builder.build();
+        final DefaultFeatureType type2 = builder.build();
         featureSet2 = new MemoryFeatureSet(null, type2, Arrays.asList(
                 newFeature2(type2, "fid_2_0",  1, 10),
                 newFeature2(type2, "fid_2_1",  2, 20),
@@ -96,8 +96,8 @@
      * Creates a new feature of type 1 with the given identifier and attribute values.
      * This is a helper method for the constructor only.
      */
-    private static Feature newFeature1(final FeatureType type, final String id, final String att1, final int att2) {
-        final Feature f = type.newInstance();
+    private static AbstractFeature newFeature1(final DefaultFeatureType type, final String id, final String att1, final int att2) {
+        final AbstractFeature f = type.newInstance();
         f.setPropertyValue(AttributeConvention.IDENTIFIER, id);
         f.setPropertyValue("att1", att1);
         f.setPropertyValue("att2", att2);
@@ -108,8 +108,8 @@
      * Creates a new feature of type 2 with the given identifier and attribute values.
      * This is a helper method for the constructor only.
      */
-    private static Feature newFeature2(final FeatureType type, final String id, final int att3, final double att4) {
-        final Feature f = type.newInstance();
+    private static AbstractFeature newFeature2(final DefaultFeatureType type, final String id, final int att3, final double att4) {
+        final AbstractFeature f = type.newInstance();
         f.setPropertyValue(AttributeConvention.IDENTIFIER, id);
         f.setPropertyValue("att3", att3);
         f.setPropertyValue("att4", att4);
@@ -120,15 +120,15 @@
      * Creates a new join feature set of the given type using the {@link #featureSet1} and {@link #featureSet2}.
      */
     private FeatureSet create(final JoinFeatureSet.Type type) throws DataStoreException {
-        final FilterFactory<Feature, Object, ?> factory = DefaultFilterFactory.forFeatures();
-        final BinaryComparisonOperator<Feature> condition = factory.equal(
+        final DefaultFilterFactory<AbstractFeature, Object, ?> factory = DefaultFilterFactory.forFeatures();
+        final Filter<AbstractFeature> condition = factory.equal(
                 factory.property("att2", String.class),
-                factory.property("att3", String.class),
-                true, MatchAction.ANY);
+                factory.property("att3", String.class));
         final Map<String,Object> properties = new HashMap<>(4);
         assertNull(properties.put("name", "JoinSet"));
         assertNull(properties.put("identifierDelimiter", " "));
-        return new JoinFeatureSet(null, featureSet1, "s1", featureSet2, "s2", type, condition, properties);
+        return new JoinFeatureSet(null, featureSet1, "s1", featureSet2, "s2", type,
+                (BinaryComparisonOperator<AbstractFeature>) condition, properties);
     }
 
     /**
@@ -136,7 +136,7 @@
      * then this method copies the features in a temporary list using parallelized paths
      * before to return the stream of that list.
      */
-    private Stream<Feature> stream(final FeatureSet col) throws DataStoreException {
+    private Stream<AbstractFeature> stream(final FeatureSet col) throws DataStoreException {
         if (parallel) {
             return col.features(true).collect(Collectors.toList()).stream();
         } else {
@@ -147,7 +147,7 @@
     /**
      * Returns the identifier of the given feature.
      */
-    private static String getId(final Feature feature) {
+    private static String getId(final AbstractFeature feature) {
         return String.valueOf(feature.getPropertyValue(AttributeConvention.IDENTIFIER));
     }
 
@@ -159,12 +159,12 @@
     @Test
     public void testInnerJoin() throws DataStoreException {
         final FeatureSet col = create(JoinFeatureSet.Type.INNER);
-        try (Stream<Feature> stream = stream(col)) {
-            final Iterator<Feature> ite = stream.iterator();
+        try (Stream<AbstractFeature> stream = stream(col)) {
+            final Iterator<AbstractFeature> ite = stream.iterator();
             int count = 0;
             while (ite.hasNext()) {
                 count++;
-                final Feature f = ite.next();
+                final AbstractFeature f = ite.next();
                 final String att1;          // Expected value of "att1".
                 final int    join;          // Expected value of "att2" and "att3", on which the join operation is done.
                 final double att4;          // Expected value of "att4".
@@ -175,12 +175,12 @@
                     case "fid_1_2 fid_2_3":  att1 = "str3"; join = 3; att4 = 40; break;
                     default: fail("unexpected feature"); continue;
                 }
-                final Feature c1 = (Feature) f.getPropertyValue("s1");
-                final Feature c2 = (Feature) f.getPropertyValue("s2");
-                assertEquals("att1", att1, c1.getProperty("att1").getValue());
-                assertEquals("att2", join, c1.getProperty("att2").getValue());
-                assertEquals("att3", join, c2.getProperty("att3").getValue());
-                assertEquals("att4", att4, c2.getProperty("att4").getValue());
+                final AbstractFeature c1 = (AbstractFeature) f.getPropertyValue("s1");
+                final AbstractFeature c2 = (AbstractFeature) f.getPropertyValue("s2");
+                assertEquals("att1", att1, ((AbstractAttribute) c1.getProperty("att1")).getValue());
+                assertEquals("att2", join, ((AbstractAttribute) c1.getProperty("att2")).getValue());
+                assertEquals("att3", join, ((AbstractAttribute) c2.getProperty("att3")).getValue());
+                assertEquals("att4", att4, ((AbstractAttribute) c2.getProperty("att4")).getValue());
             }
             assertEquals("Unexpected amount of features.", 4, count);
         }
@@ -215,46 +215,46 @@
      * @param  nr  1 if testing outer right, 0 otherwise.
      */
     private void testOuter(final FeatureSet col, final int nl, final int nr) throws DataStoreException {
-        try (Stream<Feature> stream = stream(col)) {
-            final Iterator<Feature> ite = stream.iterator();
+        try (Stream<AbstractFeature> stream = stream(col)) {
+            final Iterator<AbstractFeature> ite = stream.iterator();
             int foundStr1 = 0, foundStr20 = 0, foundStr50 = 0, foundStr60 = 0,
                 foundStr3 = 0, foundStr21 = 0, foundStr51 = 0, foundStr61 = 0, count = 0;
             while (ite.hasNext()) {
-                final Feature f  = ite.next();
-                final Feature c1 = (Feature) f.getPropertyValue("s1");
-                final Feature c2 = (Feature) f.getPropertyValue("s2");
+                final AbstractFeature f  = ite.next();
+                final AbstractFeature c1 = (AbstractFeature) f.getPropertyValue("s1");
+                final AbstractFeature c2 = (AbstractFeature) f.getPropertyValue("s2");
                 if (c1 != null) {
-                    switch ((String) c1.getProperty("att1").getValue()) {
+                    switch ((String) ((AbstractAttribute) c1.getProperty("att1")).getValue()) {
                         case "str1": {
-                            assertEquals("att2",  1,  c1.getProperty("att2").getValue());
-                            assertEquals("att3",  1,  c2.getProperty("att3").getValue());
-                            assertEquals("att4", 10d, c2.getProperty("att4").getValue());
+                            assertEquals("att2",  1,  ((AbstractAttribute) c1.getProperty("att2")).getValue());
+                            assertEquals("att3",  1,  ((AbstractAttribute) c2.getProperty("att3")).getValue());
+                            assertEquals("att4", 10d, ((AbstractAttribute) c2.getProperty("att4")).getValue());
                             foundStr1++;
                             break;
                         }
                         case "str2": {
-                            assertEquals("att2", 2, c1.getProperty("att2").getValue());
-                            assertEquals("att3", 2, c2.getProperty("att3").getValue());
-                            double att4 = (Double)  c2.getProperty("att4").getValue();
+                            assertEquals("att2", 2, ((AbstractAttribute) c1.getProperty("att2")).getValue());
+                            assertEquals("att3", 2, ((AbstractAttribute) c2.getProperty("att3")).getValue());
+                            double att4 = (Double)  ((AbstractAttribute) c2.getProperty("att4")).getValue();
                             if (att4 == 20) foundStr20++;
                             if (att4 == 30) foundStr21++;
                             break;
                         }
                         case "str3": {
-                            assertEquals("att2",  3,  c1.getProperty("att2").getValue());
-                            assertEquals("att3",  3,  c2.getProperty("att3").getValue());
-                            assertEquals("att4", 40d, c2.getProperty("att4").getValue());
+                            assertEquals("att2",  3,  ((AbstractAttribute) c1.getProperty("att2")).getValue());
+                            assertEquals("att3",  3,  ((AbstractAttribute) c2.getProperty("att3")).getValue());
+                            assertEquals("att4", 40d, ((AbstractAttribute) c2.getProperty("att4")).getValue());
                             foundStr3++;
                             break;
                         }
                         case "str50": {
-                            assertEquals("att2", 50, c1.getProperty("att2").getValue());
+                            assertEquals("att2", 50, ((AbstractAttribute) c1.getProperty("att2")).getValue());
                             assertNull("right", c2);
                             foundStr50++;
                             break;
                         }
                         case "str51": {
-                            assertEquals("att2", 51, c1.getProperty("att2").getValue());
+                            assertEquals("att2", 51, ((AbstractAttribute) c1.getProperty("att2")).getValue());
                             assertNull("right", c2);
                             foundStr51++;
                             break;
@@ -265,14 +265,14 @@
                         }
                     }
                 } else {
-                    switch ((Integer) c2.getProperty("att3").getValue()) {
+                    switch ((Integer) ((AbstractAttribute) c2.getProperty("att3")).getValue()) {
                         case 60: {
-                            assertEquals(c2.getProperty("att4").getValue(), 60d);
+                            assertEquals(((AbstractAttribute) c2.getProperty("att4")).getValue(), 60d);
                             foundStr60++;
                             break;
                         }
                         case 61: {
-                            assertEquals(c2.getProperty("att4").getValue(), 61d);
+                            assertEquals(((AbstractAttribute) c2.getProperty("att4")).getValue(), 61d);
                             foundStr61++;
                             break;
                         }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java
index 24dfdf5..6c2c8ea 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java
@@ -20,11 +20,12 @@
 import org.opengis.util.GenericName;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.content.ContentInformation;
-import org.opengis.metadata.content.FeatureCatalogueDescription;
-import org.opengis.metadata.content.FeatureTypeInfo;
-import org.opengis.metadata.constraint.LegalConstraints;
 import org.opengis.metadata.constraint.Restriction;
 import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.constraint.DefaultLegalConstraints;
+import org.apache.sis.metadata.iso.content.DefaultFeatureCatalogueDescription;
+import org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo;
 import org.apache.sis.feature.DefaultFeatureType;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
@@ -33,9 +34,6 @@
 import static org.apache.sis.test.TestUtilities.date;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Branch-dependent imports
-import org.opengis.feature.FeatureType;
-
 
 /**
  * Tests {@link MetadataBuilder}.
@@ -83,18 +81,18 @@
     private static void verifyCopyrightParsing(final String notice) {
         final MetadataBuilder builder = new MetadataBuilder();
         builder.parseLegalNotice(notice);
-        final LegalConstraints constraints = (LegalConstraints) getSingleton(getSingleton(
+        final DefaultLegalConstraints constraints = (DefaultLegalConstraints) getSingleton(getSingleton(
                 builder.build().getIdentificationInfo()).getResourceConstraints());
 
         assertEquals("useConstraints", Restriction.COPYRIGHT, getSingleton(constraints.getUseConstraints()));
         final Citation ref = getSingleton(constraints.getReferences());
         assertTitleEquals("reference.title", notice, ref);
-        assertPartyNameEquals("reference.citedResponsibleParty", "John Smith", ref);
+        assertPartyNameEquals("reference.citedResponsibleParty", "John Smith", (DefaultCitation) ref);
         assertEquals("date", date("1992-01-01 00:00:00"), getSingleton(ref.getDates()).getDate());
     }
 
     /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
+     * Tests {@link MetadataBuilder#addFeatureType(DefaultFeatureType, long)}.
      *
      * @todo Combine the 4 tests in a single one for leveraging the same {@link DefaultFeatureType} instance?
      *       It would be consistent with {@link #testParseLegalNotice()}, and the error message in those tests
@@ -106,7 +104,7 @@
     }
 
     /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
+     * Tests {@link MetadataBuilder#addFeatureType(DefaultFeatureType, long)}.
      */
     @Test
     public void no_overflow_on_feature_count() {
@@ -114,7 +112,7 @@
     }
 
     /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
+     * Tests {@link MetadataBuilder#addFeatureType(DefaultFeatureType, long)}.
      */
     @Test
     public void verify_feature_count_is_written() {
@@ -122,7 +120,7 @@
     }
 
     /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
+     * Tests {@link MetadataBuilder#addFeatureType(DefaultFeatureType, long)}.
      */
     @Test
     public void feature_should_be_ignored_when_count_is_zero() {
@@ -131,7 +129,7 @@
 
     /**
      * Creates a new simple metadata with a single simple feature type and the given
-     * {@linkplain FeatureTypeInfo#getFeatureInstanceCount() feature instance count}.
+     * {@linkplain DefaultFeatureTypeInfo#getFeatureInstanceCount() feature instance count}.
      * Then, asserts that the value in the built metadata is compliant with a given control value.
      *
      * @param expected       the feature instance count value we want to see in the metadata (control value).
@@ -149,8 +147,8 @@
             assertTrue(metadata.getContentInfo().isEmpty());
         } else {
             final ContentInformation content = getSingleton(metadata.getContentInfo());
-            assertInstanceOf("Metadata.contentInfo", FeatureCatalogueDescription.class, content);
-            final FeatureTypeInfo info = getSingleton(((FeatureCatalogueDescription) content).getFeatureTypeInfo());
+            assertInstanceOf("Metadata.contentInfo", DefaultFeatureCatalogueDescription.class, content);
+            final DefaultFeatureTypeInfo info = getSingleton(((DefaultFeatureCatalogueDescription) content).getFeatureTypeInfo());
             assertEquals(errorMessage, expected, info.getFeatureInstanceCount());
         }
     }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
index 160c39d..06f0768 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
@@ -38,10 +38,11 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
 
 
 /**
@@ -100,7 +101,7 @@
         try (Store store = open()) {
             metadata = store.getMetadata();
         }
-        final Extent extent = getSingleton(getSingleton(metadata.getIdentificationInfo()).getExtents());
+        final Extent extent = getSingleton(((AbstractIdentification) getSingleton(metadata.getIdentificationInfo())).getExtents());
         final GeographicBoundingBox bbox = (GeographicBoundingBox) getSingleton(extent.getGeographicElements());
         assertEquals("westBoundLongitude", 50.23, bbox.getWestBoundLongitude(), STRICT);
         assertEquals("eastBoundLongitude", 50.31, bbox.getEastBoundLongitude(), STRICT);
@@ -119,7 +120,7 @@
         try (Store store = open()) {
             verifyFeatureType(store.featureType, double[].class, 1);
             assertEquals("foliation", Foliation.TIME, store.foliation);
-            final Iterator<Feature> it = store.features(false).iterator();
+            final Iterator<AbstractFeature> it = store.features(false).iterator();
             assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:11", new double[] {11, 2, 12, 3},        "walking", 1);
             assertPropertyEquals(it.next(), "b", "12:33:51", "12:36:51", new double[] {10, 2, 11, 3},        "walking", 2);
             assertPropertyEquals(it.next(), "a", "12:36:11", "12:36:51", new double[] {12, 3, 10, 3},        "walking", 2);
@@ -148,7 +149,7 @@
         try (Store store = new Store(null, new StorageConnector(testData()))) {
             verifyFeatureType(store.featureType, Polyline.class, Integer.MAX_VALUE);
             assertEquals("foliation", Foliation.TIME, store.foliation);
-            final Iterator<Feature> it = store.features(false).iterator();
+            final Iterator<AbstractFeature> it = store.features(false).iterator();
             assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:51", new double[] {11, 2, 12, 3, 10, 3}, singletonList("walking"), Arrays.asList(1, 2));
             assertPropertyEquals(it.next(), "b", "12:33:51", "12:36:51", new double[] {10, 2, 11, 3},        singletonList("walking"), singletonList(2));
             assertPropertyEquals(it.next(), "c", "12:33:51", "12:36:51", new double[] {12, 1, 10, 2, 11, 3}, singletonList("vehicle"), singletonList(1));
@@ -159,21 +160,21 @@
     /**
      * Verifies that the feature type is equal to the expected one.
      */
-    private static void verifyFeatureType(final FeatureType type, final Class<?> geometryType, final int maxOccurs) {
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "mfidref",       String.class,   1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "startTime",     Instant.class,  1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "endTime",       Instant.class,  1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "trajectory",    geometryType,   1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "state",         String.class,   0, maxOccurs);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "\"type\" code", Integer.class,  0, maxOccurs);
+    private static void verifyFeatureType(final DefaultFeatureType type, final Class<?> geometryType, final int maxOccurs) {
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "mfidref",       String.class,   1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "startTime",     Instant.class,  1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "endTime",       Instant.class,  1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "trajectory",    geometryType,   1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "state",         String.class,   0, maxOccurs);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "\"type\" code", Integer.class,  0, maxOccurs);
         assertFalse(it.hasNext());
     }
 
     /**
      * Asserts that the given property type has the given information.
      */
-    private static void assertPropertyTypeEquals(final AttributeType<?> p,
+    private static void assertPropertyTypeEquals(final DefaultAttributeType<?> p,
             final String name, final Class<?> valueClass, final int minOccurs, final int maxOccurs)
     {
         assertEquals("name",       name,       p.getName().toString());
@@ -185,7 +186,7 @@
     /**
      * Asserts that the property of the given name in the given feature has expected information.
      */
-    private void assertPropertyEquals(final Feature f, final String mfidref,
+    private void assertPropertyEquals(final AbstractFeature f, final String mfidref,
             final String startTime, final String endTime, final double[] trajectory,
             final Object state, final Object typeCode)
     {
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/esri/AsciiGridStoreTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/esri/AsciiGridStoreTest.java
index ee5d64b..6c84085 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/esri/AsciiGridStoreTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/esri/AsciiGridStoreTest.java
@@ -21,7 +21,7 @@
 import java.awt.image.RenderedImage;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.metadata.identification.Identification;
+import org.opengis.metadata.identification.DataIdentification;
 import org.apache.sis.coverage.Category;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.storage.DataStoreException;
@@ -79,9 +79,7 @@
              * Format information is hard-coded in "SpatialMetadata" database. Complete string should
              * be "ESRI ArcInfo ASCII Grid format" but it depends on the presence of Derby dependency.
              */
-            final Identification id = getSingleton(metadata.getIdentificationInfo());
-            final String format = getSingleton(id.getResourceFormats()).getFormatSpecificationCitation().getTitle().toString();
-            assertTrue(format, format.contains("ASCII Grid"));
+            final DataIdentification id = (DataIdentification) getSingleton(metadata.getIdentificationInfo());
             /*
              * This information should have been read from the PRJ file.
              */
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/image/WorldFileStoreTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/image/WorldFileStoreTest.java
index ac413bf..aac0db2 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/image/WorldFileStoreTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/image/WorldFileStoreTest.java
@@ -23,7 +23,7 @@
 import java.nio.file.StandardOpenOption;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.metadata.identification.Identification;
+import org.opengis.metadata.identification.DataIdentification;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.GridCoverageResource;
@@ -93,9 +93,7 @@
              */
             assertEquals("gradient", store.getIdentifier().get().toString());
             final Metadata metadata = store.getMetadata();
-            final Identification id = getSingleton(metadata.getIdentificationInfo());
-            final String format = getSingleton(id.getResourceFormats()).getFormatSpecificationCitation().getTitle().toString();
-            assertTrue(format, format.contains("PNG"));
+            final DataIdentification id = (DataIdentification) getSingleton(metadata.getIdentificationInfo());
             assertEquals("WGS 84", getSingleton(metadata.getReferenceSystemInfo()).getName().getCode());
             final GeographicBoundingBox bbox = (GeographicBoundingBox)
                     getSingleton(getSingleton(id.getExtents()).getGeographicElements());
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/wkt/StoreTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/wkt/StoreTest.java
index 44ee1c7..ddc13a4 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/wkt/StoreTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/wkt/StoreTest.java
@@ -29,7 +29,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java
index 094e32e..ab742f1 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java
@@ -18,9 +18,9 @@
 
 import java.util.Locale;
 import java.io.StringReader;
-import java.nio.charset.StandardCharsets;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.citation.*;
+import org.opengis.metadata.identification.CharacterSet;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
@@ -68,7 +68,7 @@
             "          <gmd:onlineResource>\n" +
             "            <gmd:CI_OnlineResource>\n" +
             "              <gmd:linkage>\n" +
-            "                <gmd:URL>https://sis.apache.org</gmd:URL>\n" +
+            "                <gmd:URL>http://sis.apache.org</gmd:URL>\n" +
             "              </gmd:linkage>\n" +
             "              <gmd:function>\n" +
             "                <gmd:CI_OnLineFunctionCode codeListValue=\"information\" codeSpace=\"fra\">Information</gmd:CI_OnLineFunctionCode>\n" +
@@ -96,17 +96,15 @@
             metadata = store.getMetadata();
             assertSame("Expected cached value.", metadata, store.getMetadata());
         }
-        final Responsibility resp     = getSingleton(metadata.getContacts());
-        final Party          party    = getSingleton(resp.getParties());
-        final Contact        contact  = getSingleton(party.getContactInfo());
-        final OnlineResource resource = getSingleton(contact.getOnlineResources());
+        final ResponsibleParty resp     = getSingleton(metadata.getContacts());
+        final Contact          contact  = resp.getContactInfo();
+        final OnlineResource   resource = contact.getOnlineResource();
 
-        assertInstanceOf("party", Organisation.class, party);
-        assertEquals(Locale.ENGLISH,              getSingleton(metadata.getLocalesAndCharsets().keySet()));
-        assertEquals(StandardCharsets.UTF_8,      getSingleton(metadata.getLocalesAndCharsets().values()));
+        assertEquals(Locale.ENGLISH,              metadata.getLanguage());
+        assertEquals(CharacterSet.UTF_8,          metadata.getCharacterSet());
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, resp.getRole());
-        assertEquals("Apache SIS",                String.valueOf(party.getName()));
-        assertEquals("https://sis.apache.org",    String.valueOf(resource.getLinkage()));
+        assertEquals("Apache SIS",                String.valueOf(resp.getOrganisationName()));
+        assertEquals("http://sis.apache.org",     String.valueOf(resource.getLinkage()));
         assertEquals(OnLineFunction.INFORMATION,  resource.getFunction());
     }
 }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java b/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
index a4d157d..0f79898 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
@@ -30,15 +30,10 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.MatchAction;
-import org.opengis.filter.SortOrder;
-import org.opengis.filter.SortProperty;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -55,7 +50,7 @@
     /**
      * An arbitrary amount of features, all of the same type.
      */
-    private final Feature[] features;
+    private final AbstractFeature[] features;
 
     /**
      * The {@link #features} array wrapped in a in-memory feature set.
@@ -76,15 +71,15 @@
         // A dependency of the test feature type.
         ftb = new FeatureTypeBuilder().setName("Dependency");
         ftb.addAttribute(Integer.class).setName("value3");
-        final FeatureType dependency = ftb.build();
+        final DefaultFeatureType dependency = ftb.build();
 
         // Test feature type with attributes and association.
         ftb = new FeatureTypeBuilder().setName("Test");
         ftb.addAttribute(Integer.class).setName("value1");
         ftb.addAttribute(Integer.class).setName("value2");
         ftb.addAssociation(dependency).setName("dependency");
-        final FeatureType type = ftb.build();
-        features = new Feature[] {
+        final DefaultFeatureType type = ftb.build();
+        features = new AbstractFeature[] {
             feature(type, null,       3, 1,  0),
             feature(type, null,       2, 2,  0),
             feature(type, dependency, 2, 1, 25),
@@ -99,14 +94,14 @@
      * Creates an instance of the test feature type with the given values.
      * The {@code value3} is stored only if {@code dependency} is non-null.
      */
-    private static Feature feature(final FeatureType type, final FeatureType dependency,
+    private static AbstractFeature feature(final DefaultFeatureType type, final DefaultFeatureType dependency,
                                    final int value1, final int value2, final int value3)
     {
-        final Feature f = type.newInstance();
+        final AbstractFeature f = type.newInstance();
         f.setPropertyValue("value1", value1);
         f.setPropertyValue("value2", value2);
         if (dependency != null) {
-            final Feature d = dependency.newInstance();
+            final AbstractFeature d = dependency.newInstance();
             d.setPropertyValue("value3", value3);
             f.setPropertyValue("dependency", d);
         }
@@ -116,7 +111,7 @@
     /**
      * Configures the query for returning a single instance and returns that instance.
      */
-    private Feature executeAndGetFirst() throws DataStoreException {
+    private AbstractFeature executeAndGetFirst() throws DataStoreException {
         query.setLimit(1);
         final FeatureSet subset = query.execute(featureSet);
         return TestUtilities.getSingleton(subset.features(false).collect(Collectors.toList()));
@@ -130,11 +125,11 @@
      */
     private void verifyQueryResult(final int... indices) throws DataStoreException {
         final FeatureSet fs = query.execute(featureSet);
-        final List<Feature> result = fs.features(false).collect(Collectors.toList());
+        final List<AbstractFeature> result = fs.features(false).collect(Collectors.toList());
         assertEquals("size", indices.length, result.size());
         for (int i=0; i<indices.length; i++) {
-            final Feature expected = features[indices[i]];
-            final Feature actual   = result.get(i);
+            final AbstractFeature expected = features[indices[i]];
+            final AbstractFeature actual   = result.get(i);
             if (!expected.equals(actual)) {
                 fail(String.format("Unexpected feature at index %d%n"
                                  + "Expected:%n%s%n"
@@ -166,28 +161,15 @@
     }
 
     /**
-     * Verifies the effect of {@link FeatureQuery#setSortBy(SortProperty[])}.
-     *
-     * @throws DataStoreException if an error occurred while executing the query.
-     */
-    @Test
-    public void testSortBy() throws DataStoreException {
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
-        query.setSortBy(ff.sort(ff.property("value1", Integer.class), SortOrder.ASCENDING),
-                        ff.sort(ff.property("value2", Integer.class), SortOrder.DESCENDING));
-        verifyQueryResult(3, 1, 2, 0, 4);
-    }
-
-    /**
      * Verifies the effect of {@link FeatureQuery#setSelection(Filter)}.
      *
      * @throws DataStoreException if an error occurred while executing the query.
      */
     @Test
     public void testSelection() throws DataStoreException {
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setSelection(ff.equal(ff.property("value1", Integer.class),
-                                    ff.literal(2), true, MatchAction.ALL));
+                                    ff.literal(2)));
         verifyQueryResult(1, 2);
     }
 
@@ -199,7 +181,7 @@
      */
     @Test
     public void testSelectionThroughAssociation() throws DataStoreException {
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setSelection(ff.equal(ff.property("dependency/value3"), ff.literal(18)));
         verifyQueryResult(3);
     }
@@ -211,25 +193,25 @@
      */
     @Test
     public void testProjection() throws DataStoreException {
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setProjection(new FeatureQuery.NamedExpression(ff.property("value1", Integer.class), (String) null),
                             new FeatureQuery.NamedExpression(ff.property("value1", Integer.class), "renamed1"),
                             new FeatureQuery.NamedExpression(ff.literal("a literal"), "computed"));
 
         // Check result type.
-        final Feature instance = executeAndGetFirst();
-        final FeatureType resultType = instance.getType();
+        final AbstractFeature instance = executeAndGetFirst();
+        final DefaultFeatureType resultType = instance.getType();
         assertEquals("Test", resultType.getName().toString());
         assertEquals(3, resultType.getProperties(true).size());
-        final PropertyType pt1 = resultType.getProperty("value1");
-        final PropertyType pt2 = resultType.getProperty("renamed1");
-        final PropertyType pt3 = resultType.getProperty("computed");
-        assertTrue(pt1 instanceof AttributeType);
-        assertTrue(pt2 instanceof AttributeType);
-        assertTrue(pt3 instanceof AttributeType);
-        assertEquals(Integer.class, ((AttributeType) pt1).getValueClass());
-        assertEquals(Integer.class, ((AttributeType) pt2).getValueClass());
-        assertEquals(String.class,  ((AttributeType) pt3).getValueClass());
+        final AbstractIdentifiedType pt1 = resultType.getProperty("value1");
+        final AbstractIdentifiedType pt2 = resultType.getProperty("renamed1");
+        final AbstractIdentifiedType pt3 = resultType.getProperty("computed");
+        assertTrue(pt1 instanceof DefaultAttributeType);
+        assertTrue(pt2 instanceof DefaultAttributeType);
+        assertTrue(pt3 instanceof DefaultAttributeType);
+        assertEquals(Integer.class, ((DefaultAttributeType) pt1).getValueClass());
+        assertEquals(Integer.class, ((DefaultAttributeType) pt2).getValueClass());
+        assertEquals(String.class,  ((DefaultAttributeType) pt3).getValueClass());
 
         // Check feature instance.
         assertEquals(3, instance.getPropertyValue("value1"));
@@ -245,8 +227,8 @@
     @Test
     public void testProjectionByNames() throws DataStoreException {
         query.setProjection("value2");
-        final Feature instance = executeAndGetFirst();
-        final PropertyType p = TestUtilities.getSingleton(instance.getType().getProperties(true));
+        final AbstractFeature instance = executeAndGetFirst();
+        final AbstractIdentifiedType p = TestUtilities.getSingleton(instance.getType().getProperties(true));
         assertEquals("value2", p.getName().toString());
     }
 
@@ -258,19 +240,19 @@
      */
     @Test
     public void testDefaultColumnName() throws DataStoreException {
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setLimit(1);
         query.setProjection(
                 ff.add(ff.property("value1", Number.class), ff.literal(1)),
                 ff.add(ff.property("value2", Number.class), ff.literal(1)));
         final FeatureSet subset = featureSet.subset(query);
-        final FeatureType type = subset.getType();
-        final Iterator<? extends PropertyType> properties = type.getProperties(true).iterator();
+        final DefaultFeatureType type = subset.getType();
+        final Iterator<? extends AbstractIdentifiedType> properties = type.getProperties(true).iterator();
         assertEquals("Unnamed #1", properties.next().getName().toString());
         assertEquals("Unnamed #2", properties.next().getName().toString());
         assertFalse(properties.hasNext());
 
-        final Feature instance = TestUtilities.getSingleton(subset.features(false).collect(Collectors.toList()));
+        final AbstractFeature instance = TestUtilities.getSingleton(subset.features(false).collect(Collectors.toList()));
         assertSame(type, instance.getType());
     }
 
@@ -283,21 +265,21 @@
      */
     @Test
     public void testProjectionOfAbstractType() throws DataStoreException {
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setProjection(new FeatureQuery.NamedExpression(ff.property("value1"),  (String) null),
                             new FeatureQuery.NamedExpression(ff.property("/*/unknown"), "unexpected"));
 
         // Check result type.
-        final Feature instance = executeAndGetFirst();
-        final FeatureType resultType = instance.getType();
+        final AbstractFeature instance = executeAndGetFirst();
+        final DefaultFeatureType resultType = instance.getType();
         assertEquals("Test", resultType.getName().toString());
         assertEquals(2, resultType.getProperties(true).size());
-        final PropertyType pt1 = resultType.getProperty("value1");
-        final PropertyType pt2 = resultType.getProperty("unexpected");
-        assertTrue(pt1 instanceof AttributeType);
-        assertTrue(pt2 instanceof AttributeType);
-        assertEquals(Integer.class, ((AttributeType) pt1).getValueClass());
-        assertEquals(Object.class,  ((AttributeType) pt2).getValueClass());
+        final AbstractIdentifiedType pt1 = resultType.getProperty("value1");
+        final AbstractIdentifiedType pt2 = resultType.getProperty("unexpected");
+        assertTrue(pt1 instanceof DefaultAttributeType);
+        assertTrue(pt2 instanceof DefaultAttributeType);
+        assertEquals(Integer.class, ((DefaultAttributeType) pt1).getValueClass());
+        assertEquals(Object.class,  ((DefaultAttributeType) pt2).getValueClass());
 
         // Check feature property values.
         assertEquals(3,    instance.getPropertyValue("value1"));
@@ -312,11 +294,11 @@
      */
     @Test
     public void testProjectionThroughAssociation() throws DataStoreException {
-        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
         query.setProjection(new FeatureQuery.NamedExpression(ff.property("value1"),  (String) null),
                             new FeatureQuery.NamedExpression(ff.property("dependency/value3"), "value3"));
         query.setOffset(2);
-        final Feature instance = executeAndGetFirst();
+        final AbstractFeature instance = executeAndGetFirst();
         assertEquals("value1",  2, instance.getPropertyValue("value1"));
         assertEquals("value3", 25, instance.getPropertyValue("value3"));
     }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/test/storage/CoverageReadConsistency.java b/storage/sis-storage/src/test/java/org/apache/sis/test/storage/CoverageReadConsistency.java
index 8d040d8..cf9fcd0 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/test/storage/CoverageReadConsistency.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/test/storage/CoverageReadConsistency.java
@@ -367,8 +367,13 @@
              * two-dimensional image, the following loop will be executed only once. If reading a 3D
              * or 4D image, the loop is executed for all possible two-dimensional slices in the cube.
              */
-            final long[] sliceMin = actualReadExtent.getLow() .getCoordinateValues();
-            final long[] sliceMax = actualReadExtent.getHigh().getCoordinateValues();
+            final int sd = actualReadExtent.getDimension();
+            final long[] sliceMin = new long[sd];
+            final long[] sliceMax = new long[sd];
+            for (int i=0; i<sd; i++) {
+                sliceMin[i] = actualReadExtent.getLow(i);
+                sliceMax[i] = actualReadExtent.getHigh(i);
+            }
 nextSlice:  for (;;) {
                 System.arraycopy(sliceMin, BIDIMENSIONAL, sliceMax, BIDIMENSIONAL, dimension - BIDIMENSIONAL);
                 final PixelIterator itr = iterator(full,   sliceMin, sliceMax, subsampling, subOffsets, allowSubsampling);
diff --git a/storage/sis-xmlstore/pom.xml b/storage/sis-xmlstore/pom.xml
index 54d50f5..9916044 100644
--- a/storage/sis-xmlstore/pom.xml
+++ b/storage/sis-xmlstore/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.3-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Copyright.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Copyright.java
index c6ced5b..4cdf96d 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Copyright.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Copyright.java
@@ -25,22 +25,26 @@
 import java.util.Objects;
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
+import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.CitationDate;
+import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.citation.DateType;
 import org.opengis.metadata.citation.OnlineResource;
 import org.opengis.metadata.citation.PresentationForm;
 import org.opengis.metadata.citation.Role;
+import org.opengis.metadata.citation.Series;
 import org.opengis.metadata.constraint.LegalConstraints;
 import org.opengis.metadata.constraint.Restriction;
-import org.opengis.metadata.identification.BrowseGraphic;
 import org.opengis.util.InternationalString;
-import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.util.iso.Types;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.constraint.DefaultConstraints;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 
 
 /**
@@ -63,15 +67,11 @@
  * @since   0.8
  * @module
  */
-public final class Copyright implements LegalConstraints, Responsibility, Party, Citation, CitationDate {
+public final class Copyright implements LegalConstraints, ResponsibleParty, Citation, CitationDate {
     /**
      * The copyright holder.
      * This field is mandatory in principle, but {@code Copyright} implementation is robust to null value.
-     * This field is mapped to the {@linkplain #getName() responsible party name} in ISO 19115 metadata.
-     *
-     * @see #getResponsibleParties()
-     * @see #getParties()
-     * @see #getName()
+     * This field is mapped to the {@code getName() responsible party name} in ISO 19115 metadata.
      */
     @XmlAttribute(name = Attributes.AUTHOR, required = true)
     public String author;
@@ -87,9 +87,7 @@
 
     /**
      * Link to an external file containing the license text, or {@code null} if none.
-     * This field is mapped to the {@linkplain #getOnlineResources() online resources} in ISO 19115 metadata.
-     *
-     * @see #getOnlineResources()
+     * This field is mapped to the online resources in ISO 19115 metadata.
      */
     @XmlElement(name = Tags.LICENSE)
     public URI license;
@@ -105,13 +103,16 @@
      * Copies properties from the given ISO 19115 metadata.
      */
     private Copyright(final LegalConstraints c, final Locale locale) {
-resp:   for (final Responsibility r : c.getResponsibleParties()) {
-            for (final Party p : r.getParties()) {
+        if (!(c instanceof DefaultConstraints)) {
+            return;
+        }
+resp:   for (final DefaultResponsibility r : ((DefaultConstraints) c).getResponsibleParties()) {
+            for (final AbstractParty p : r.getParties()) {
                 author = Types.toString(p.getName(), locale);
                 if (author != null) break resp;
             }
         }
-        for (final Citation ci : c.getReferences()) {
+        for (final Citation ci : ((DefaultConstraints) c).getReferences()) {
             for (final CitationDate d : ci.getDates()) {
                 final Date date = d.getDate();
                 if (date != null) {
@@ -119,7 +120,8 @@
                     break;
                 }
             }
-            for (final OnlineResource r : ci.getOnlineResources()) {
+            if (!(ci instanceof DefaultCitation)) continue;
+            for (final OnlineResource r : ((DefaultCitation) ci).getOnlineResources()) {
                 license = r.getLinkage();
                 if (license != null) break;
             }
@@ -152,13 +154,11 @@
      * ISO 19115 metadata property determined by the {@link #license} field.
      *
      * @return restrictions or limitations or warnings on using the data.
-     *
-     * @see #getReferences()
      */
     @Override
     public Collection<Restriction> getUseConstraints() {
         if (license != null) {
-            return Arrays.asList(Restriction.COPYRIGHT, Restriction.LICENCE);
+            return Arrays.asList(Restriction.COPYRIGHT, Restriction.valueOf("LICENCE"));
         } else {
             return Collections.singleton(Restriction.COPYRIGHT);
         }
@@ -167,44 +167,21 @@
     /**
      * ISO 19115 metadata property not specified by GPX.
      *
-     * @return graphics or symbols indicating the constraint.
+     * @return other restrictions and legal prerequisites for accessing and using the resource.
      */
     @Override
-    public Collection<BrowseGraphic> getGraphics() {
+    public Collection<InternationalString> getOtherConstraints() {
         return Collections.emptySet();
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #year} and {@link #license} fields.
-     * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
-     * to the {@link #getDate()} and {@link #getOnlineResources()} methods.
+     * ISO 19115 metadata property not specified by GPX.
      *
-     * @return citations for the limitation of constraint.
-     *
-     * @see #getTitle()
-     * @see #getDates()
-     * @see #getPresentationForms()
-     * @see #getOnlineResources()
+     * @return limitation affecting the fitness for use of the resource.
      */
     @Override
-    public Collection<? extends Citation> getReferences() {
-        return thisOrEmpty(year != null || license != null);
-    }
-
-    /**
-     * ISO 19115 metadata property determined by the {@link #author} field.
-     * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
-     * to the {@link #getName()} method.
-     *
-     * @return parties responsible for the resource constraints.
-     *
-     * @see #getRole()
-     * @see #getParties()
-     * @see #getCitedResponsibleParties()
-     */
-    @Override
-    public Collection<? extends Responsibility> getResponsibleParties() {
-        return thisOrEmpty(author != null);
+    public Collection<InternationalString> getUseLimitations() {
+        return Collections.emptySet();
     }
 
 
@@ -215,7 +192,7 @@
 
     /**
      * ISO 19115 metadata property fixed to {@link Role#OWNER}.
-     * This is part of the properties returned by {@link #getResponsibleParties()}.
+     * This is part of the properties returned by {@code getResponsibleParties()}.
      *
      * @return function performed by the responsible party.
      */
@@ -225,29 +202,46 @@
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #author} field.
-     * This is part of the properties returned by {@link #getResponsibleParties()}.
-     * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
-     * to the {@link #getName()} method.
+     * ISO 19115 metadata property not specified by GPX. Actually could be the {@link #author},
+     * but we have no way to know if the author is an individual or an organization.
      *
-     * @return information about the parties.
-     *
-     * @see #getName()
+     * @return name of the organization, or {@code null} if none.
      */
     @Override
-    public Collection<? extends Party> getParties() {
-        return thisOrEmpty(author != null);
+    public InternationalString getOrganisationName() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return position of the individual in the organization, or {@code null} if none.
+     */
+    @Override
+    public InternationalString getPositionName() {
+        return null;
     }
 
     /**
      * ISO 19115 metadata property determined by the {@link #author} field.
-     * This is part of the properties returned by {@link #getParties()}.
+     * This is part of the properties returned by {@code getParties()}.
      *
      * @return name of the party, or {@code null} if none.
      */
     @Override
-    public InternationalString getName() {
-        return (author != null) ? new SimpleInternationalString(author) : null;
+    public String getIndividualName() {
+        return author;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getParties()}.
+     *
+     * @return contact information for the party.
+     */
+    @Override
+    public Contact getContactInfo() {
+        return null;
     }
 
 
@@ -258,7 +252,7 @@
 
     /**
      * ISO 19115 metadata property not specified by GPX.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * This is part of the properties returned by {@code getReferences()}.
      * It would be the license title if that information was provided.
      *
      * @return the license name.
@@ -269,8 +263,19 @@
     }
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return other names for the resource.
+     */
+    @Override
+    public Collection<InternationalString> getAlternateTitles() {
+        return Collections.emptySet();
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #year} field.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * This is part of the properties returned by {@code getReferences()}.
      * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
      * to the {@link #getDate()} method.
      *
@@ -299,19 +304,64 @@
     }
 
     /**
-     * ISO 19115 metadata property fixed to {@link DateType#IN_FORCE}.
+     * ISO 19115 metadata property fixed to {@code DateType.IN_FORCE}.
      * This is part of the properties returned by {@link #getDates()}.
      *
      * @return event used for reference date.
      */
     @Override
     public DateType getDateType() {
-        return DateType.IN_FORCE;
+        return DateType.valueOf("IN_FORCE");
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the license version, or {@code null} if none.
+     */
+    @Override
+    public InternationalString getEdition() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the license edition date, or {@code null} if none.
+     */
+    @Override
+    public Date getEditionDate() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the identifiers of the license.
+     */
+    @Override
+    public Collection<Identifier> getIdentifiers() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     * It would be the license author if that information was provided.
+     *
+     * @return the information for individuals or organisations that are responsible for the license.
+     */
+    @Override
+    public Collection<ResponsibleParty> getCitedResponsibleParties() {
+        return Collections.emptySet();
     }
 
     /**
      * ISO 19115 metadata property fixed to {@link PresentationForm#DOCUMENT_DIGITAL}.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * This is part of the properties returned by {@code getReferences()}.
      *
      * @return the presentation mode of the license.
      */
@@ -321,14 +371,59 @@
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #license} field.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
      *
-     * @return online references to the cited resource.
+     * @return the series or aggregate dataset of which the dataset is a part.
      */
     @Override
-    public Collection<OnlineResource> getOnlineResources() {
-        return (license != null) ? Collections.singleton(new Link(license)) : Collections.emptySet();
+    public Series getSeries() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return other details.
+     */
+    @Override
+    public InternationalString getOtherCitationDetails() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the common title.
+     */
+    @Override
+    @Deprecated
+    public InternationalString getCollectiveTitle() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the International Standard Book Number.
+     */
+    @Override
+    public String getISBN() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the International Standard Serial Number.
+     */
+    @Override
+    public String getISSN() {
+        return null;
     }
 
     /**
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java
index 83d044a..166da7c 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java
@@ -31,10 +31,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.Property;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -82,7 +79,7 @@
     /**
      * The expected result type to be returned by {@link #getResult()}.
      */
-    private final AttributeType<?> result;
+    private final DefaultAttributeType<?> result;
 
     /**
      * Creates a new operation which will look for geometries in the given feature association.
@@ -91,7 +88,7 @@
      * @param  association     name of the property to follow in order to get the geometries to add to a polyline.
      * @param  result          the expected result type to be returned by {@link #getResult()}.
      */
-    GroupAsPolylineOperation(final Map<String,?> identification, final String association, final AttributeType<?> result) {
+    GroupAsPolylineOperation(final Map<String,?> identification, final String association, final DefaultAttributeType<?> result) {
         super(identification);
         this.association = association;
         this.result = result;
@@ -103,7 +100,7 @@
      *
      * @param  geometries  accessor to the geometry implementation in use (Java2D, ESRI or JTS).
      */
-    static <G> AttributeType<? extends G> getResult(final Geometries<G> geometries) {
+    static <G> DefaultAttributeType<? extends G> getResult(final Geometries<G> geometries) {
         return new DefaultAttributeType<>(Collections.singletonMap(NAME_KEY, AttributeConvention.ENVELOPE_PROPERTY),
                 geometries.polylineClass, 1, 1, null);
     }
@@ -120,7 +117,7 @@
      * Returns the expected result type.
      */
     @Override
-    public final AttributeType<?> getResult() {
+    public final DefaultAttributeType<?> getResult() {
         return result;
     }
 
@@ -131,7 +128,7 @@
      */
     @Override
     @SuppressWarnings({"rawtypes", "unchecked"})
-    public final Property apply(Feature feature, ParameterValueGroup parameters) {
+    public final Object apply(AbstractFeature feature, ParameterValueGroup parameters) {
         return new Result(feature, association, result);
     }
 
@@ -139,7 +136,7 @@
     /**
      * The attribute resulting from execution if the {@link GroupAsPolylineOperation}.
      * The value is computed when first requested, then cached for this {@code Result} instance only.
-     * Note that the cache is not used when {@link #apply(Feature, ParameterValueGroup)} is invoked,
+     * Note that the cache is not used when {@code apply(Feature, ParameterValueGroup)} is invoked,
      * causing a new value to be computed again. The intent is to behave as if the operation has been
      * executed at {@code apply(…)} invocation time, even if we deferred the actual execution.
      *
@@ -154,7 +151,7 @@
         /**
          * The feature on which to execute the operation.
          */
-        private final Feature feature;
+        private final AbstractFeature feature;
 
         /**
          * Name of the property to follow in order to get the geometries to add to a polyline.
@@ -171,7 +168,7 @@
          * Creates a new result for an execution on the given feature.
          * The actual computation is deferred to the first call of {@link #getValue()}.
          */
-        Result(final Feature feature, final String association, final AttributeType<G> result) {
+        Result(final AbstractFeature feature, final String association, final DefaultAttributeType<G> result) {
             super(result);
             this.feature = feature;
             this.association = association;
@@ -193,7 +190,7 @@
                     }
 
                     @Override public Object next() {
-                        return ((Feature) it.next()).getPropertyValue(AttributeConvention.GEOMETRY);
+                        return ((AbstractFeature) it.next()).getPropertyValue(AttributeConvention.GEOMETRY);
                     }
                 });
                 geometry = getType().getValueClass().cast(geom);
@@ -206,7 +203,7 @@
          */
         @Override
         public void setValue(G value) {
-            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, Attribute.class));
+            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, AbstractAttribute.class));
         }
     }
 }
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Link.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Link.java
index efe2fef..27c0108 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Link.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Link.java
@@ -158,6 +158,26 @@
     }
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return connection protocol to be used.
+     */
+    @Override
+    public String getProtocol() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return application profile that can be used with the online resource.
+     */
+    @Override
+    public String getApplicationProfile() {
+        return null;
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #text} field.
      *
      * @return name of the online resource.
@@ -188,16 +208,6 @@
     }
 
     /**
-     * ISO 19115 metadata property not specified by GPX.
-     *
-     * @return request used to access the resource, or {@code null}.
-     */
-    @Override
-    public String getProtocolRequest() {
-        return null;
-    }
-
-    /**
      * Compares this {@code Link} with the given object for equality.
      *
      * @param  obj  the object to compare with this {@code Link}.
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Metadata.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Metadata.java
index 2a3be53..dc056ee 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Metadata.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Metadata.java
@@ -45,7 +45,6 @@
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.internal.simple.SimpleMetadata;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
-import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.iso.citation.DefaultCitationDate;
 import org.apache.sis.metadata.iso.identification.DefaultKeywords;
 import org.apache.sis.metadata.iso.extent.Extents;
@@ -54,8 +53,8 @@
 import org.apache.sis.util.iso.Types;
 
 // Branch-dependent imports
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.opengis.metadata.citation.ResponsibleParty;
-import org.opengis.metadata.citation.Responsibility;
 
 
 /**
@@ -137,8 +136,6 @@
 
     /**
      * URLs associated with the location described in the file, or {@code null} if none.
-     *
-     * @see #getOnlineResources()
      */
     @XmlElement(name = Tags.LINK)
     public List<Link> links;
@@ -211,8 +208,10 @@
                         if (time != null) break;
                     }
                 }
-                for (final OnlineResource r : ci.getOnlineResources()) {
-                    links = addIfNonNull(links, Link.castOrCopy(r, locale));
+                if (ci instanceof DefaultCitation) {
+                    for (final OnlineResource r : ((DefaultCitation) ci).getOnlineResources()) {
+                        links = addIfNonNull(links, Link.castOrCopy(r, locale));
+                    }
                 }
             }
             if (description == null) {
@@ -222,7 +221,7 @@
              * identificationInfo.pointOfContact.party.name   →   creator        if role is ORIGINATOR
              * identificationInfo.pointOfContact.party.name   →   author.name    if role is AUTHOR
              */
-            for (final Responsibility r : id.getPointOfContacts()) {
+            for (final ResponsibleParty r : id.getPointOfContacts()) {
                 final Person p = Person.castOrCopy(r, locale);
                 if (p != null) {
                     if (p.isCreator) {
@@ -376,17 +375,6 @@
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #links} field.
-     * This is part of the information returned by {@link #getCitation()}.
-     *
-     * @return online references to the cited resource.
-     */
-    @Override
-    public Collection<OnlineResource> getOnlineResources() {
-        return (links != null) ? Collections.unmodifiableList(links) : Collections.emptyList();
-    }
-
-    /**
      * Names of features types available for the GPX format, or an empty list if none.
      * This property is not part of metadata described in GPX file; it is rather a hard-coded value shared by all
      * GPX files. Users could however filter the list of features, for example with only routes and no tracks.
@@ -421,16 +409,6 @@
     }
 
     /**
-     * Citation(s) for the standard(s) to which the metadata conform.
-     *
-     * @return ISO 19115-1 citation.
-     */
-    @Override
-    public Collection<Citation> getMetadataStandards() {
-        return Collections.singleton(Citations.ISO_19115.get(0));
-    }
-
-    /**
      * Compares this {@code Metadata} with the given object for equality.
      *
      * @param  obj  the object to compare with this {@code Metadata}.
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Person.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Person.java
index e39ea73..8f16893 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Person.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Person.java
@@ -16,10 +16,8 @@
  */
 package org.apache.sis.internal.storage.gpx;
 
-import java.util.AbstractSet;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.Locale;
 import java.util.Objects;
 import javax.xml.bind.annotation.XmlElement;
@@ -30,12 +28,8 @@
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.Telephone;
 import org.opengis.util.InternationalString;
-import org.apache.sis.util.SimpleInternationalString;
-import org.apache.sis.util.iso.Types;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.citation.ResponsibleParty;
 
 
@@ -52,8 +46,8 @@
  * Those properties can be read or modified directly. All methods defined in this class are bridges to
  * the ISO 19115 metadata model and can be ignored if the user only wants to manipulate the GPX model.
  *
- * <p>Note that {@link Party} is an abstract type in ISO 19115 model. We are supposed to implement a subtype
- * ({@link org.opengis.metadata.citation.Individual} or {@link org.opengis.metadata.citation.Organisation}).
+ * <p>Note that {@code Party} is an abstract type in ISO 19115 model. We are supposed to implement a subtype
+ * ({@code Individual} or {@code Organisation}).
  * However the GPX metadata does not specifies whether the "person" is actually an individual or an organization.
  * In this situation of doubt, we do not select a subtype for avoiding to provide a wrong information.</p>
  *
@@ -63,11 +57,9 @@
  * @since   0.8
  * @module
  */
-public final class Person implements ResponsibleParty, Party, Contact, Address {
+public final class Person implements ResponsibleParty, Contact, Address {
     /**
      * Name of person or organization.
-     *
-     * @see #getName()
      */
     @XmlElement(name = Tags.NAME)
     public String name;
@@ -88,8 +80,6 @@
 
     /**
      * Link to Web site or other external information about person.
-     *
-     * @see #getOnlineResources()
      */
     @XmlElement(name = Tags.LINK)
     public Link link;
@@ -116,21 +106,19 @@
      * @param  locale  the locale to use for localized strings.
      * @return the GPX metadata, or {@code null}.
      */
-    public static Person castOrCopy(final Responsibility r, final Locale locale) {
+    public static Person castOrCopy(final ResponsibleParty r, final Locale locale) {
         if (r == null || r instanceof Person) {
             return (Person) r;
         }
         final Role role = r.getRole();
         final boolean isCreator = Role.ORIGINATOR.equals(role);
         if (isCreator || Role.AUTHOR.equals(role)) {
-            for (final Party p : r.getParties()) {
-                final String name = Types.toString(p.getName(), locale);
-                if (name != null) {
-                    final Person pr = new Person();
-                    pr.name = name;
-                    pr.isCreator = isCreator;
-                    return pr;
-                }
+            final String name = r.getIndividualName();
+            if (name != null) {
+                final Person pr = new Person();
+                pr.name = name;
+                pr.isCreator = isCreator;
+                return pr;
             }
         }
         return null;
@@ -146,21 +134,6 @@
         return isCreator ? Role.ORIGINATOR : Role.AUTHOR;
     }
 
-    /**
-     * ISO 19115 metadata property determined by the {@link #name}, {@link #email} and {@link #link} fields.
-     * Invoking this method is one of the steps in the path from the {@code Responsibility} root to the
-     * {@link #getName()}, {@link #getElectronicMailAddresses()} and {@link #getOnlineResources()} methods.
-     *
-     * @return information about the parties.
-     *
-     * @see #getName()
-     * @see #getContactInfo()
-     */
-    @Override
-    public Collection<? extends Party> getParties() {
-        return thisOrEmpty(name != null || email != null || link != null);
-    }
-
 
     /* ---------------------------------------------------------------------------------
      * Implementation of the Party object returned by Responsibility.getParties().
@@ -168,16 +141,6 @@
      * --------------------------------------------------------------------------------- */
 
     /**
-     * ISO 19115 metadata property determined by the {@link #name} field.
-     *
-     * @return name of the party, or {@code null} if none.
-     */
-    @Override
-    public InternationalString getName() {
-        return (name != null) ? new SimpleInternationalString(name) : null;
-    }
-
-    /**
      * ISO 19115 metadata property not specified by GPX. Actually could be the {@link #name},
      * but we have no way to know if the author is an individual or an organization.
      *
@@ -211,36 +174,13 @@
     /**
      * ISO 19115 metadata property determined by the {@link #email} and {@link #link} fields.
      * Invoking this method is one of the steps in the path from the {@code Responsibility} root
-     * to the {@link #getElectronicMailAddresses()} and {@link #getOnlineResources()} methods.
+     * to the {@link #getElectronicMailAddresses()} and {@link #getOnlineResource()} methods.
      *
      * @return contact information for the party.
-     *
-     * @see #getAddresses()
-     * @see #getOnlineResources()
      */
     @Override
-    public Proxy getContactInfo() {        // Both Contact singleton and Collection<Contact>.
-        return new Proxy();
-    }
-
-    private final class Proxy extends AbstractSet<Contact> implements Contact {
-        @Override public int size() {
-            return (email != null || link != null) ? 1 : 0;
-        }
-
-        @Override public Iterator<Contact> iterator() {
-            return Collections.<Contact>singleton(Person.this).iterator();
-        }
-
-        @Override public Collection<? extends Telephone> getPhones()            {return Person.this.getPhones();}
-        @Override public Telephone                     getPhone()               {return Person.this.getPhone();}
-        @Override public Collection<? extends Address> getAddresses()           {return Person.this.getAddresses();}
-        @Override public Address                       getAddress()             {return Person.this.getAddress();}
-        @Override public Collection<OnlineResource>    getOnlineResources()     {return Person.this.getOnlineResources();}
-        @Override public OnlineResource                getOnlineResource()      {return Person.this.getOnlineResource();}
-        @Override public InternationalString           getHoursOfService()      {return Person.this.getHoursOfService();}
-        @Override public InternationalString           getContactInstructions() {return Person.this.getContactInstructions();}
-        @Override public InternationalString           getContactType()         {return Person.this.getContactType();}
+    public Contact getContactInfo() {
+        return (email != null || link != null) ? this : null;
     }
 
 
@@ -250,6 +190,16 @@
      * --------------------------------------------------------------------------------- */
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return telephone numbers at which the organization or individual may be contacted.
+     */
+    @Override
+    public Telephone getPhone() {
+        return null;
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #email} field.
      * Invoking this method is one of the steps in the path from the {@code Responsibility} root
      * to the {@link #getElectronicMailAddresses()} method.
@@ -259,8 +209,8 @@
      * @see #getElectronicMailAddresses()
      */
     @Override
-    public Collection<? extends Address> getAddresses() {
-        return thisOrEmpty(email != null);
+    public Address getAddress() {
+        return (email != null) ? this : null;
     }
 
     /**
@@ -269,8 +219,28 @@
      * @return on-line information that can be used to contact the individual or organization.
      */
     @Override
-    public Collection<OnlineResource> getOnlineResources() {
-        return (link != null) ? Collections.singleton(link) : Collections.emptySet();
+    public OnlineResource getOnlineResource() {
+        return link;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return time period when individuals can contact the organization or individual.
+     */
+    @Override
+    public InternationalString getHoursOfService() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return supplemental instructions on how or when to contact the individual or organization.
+     */
+    @Override
+    public InternationalString getContactInstructions() {
+        return null;
     }
 
 
@@ -280,6 +250,56 @@
      * --------------------------------------------------------------------------------- */
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return address line for the location.
+     */
+    @Override
+    public Collection<String> getDeliveryPoints() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return the city of the location.
+     */
+    @Override
+    public InternationalString getCity() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return state, province of the location.
+     */
+    @Override
+    public InternationalString getAdministrativeArea() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return ZIP or other postal code, or {@code null}.
+     */
+    @Override
+    public String getPostalCode() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return country of the physical address, or {@code null}.
+     */
+    @Override
+    public InternationalString getCountry() {
+        return null;
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #email} field.
      *
      * @return address of the electronic mailbox of the responsible organization or individual.
@@ -290,14 +310,6 @@
     }
 
     /**
-     * Returns this object as a singleton if the given condition is {@code true},
-     * or an empty set if the given condition is {@code false}.
-     */
-    private Collection<Person> thisOrEmpty(final boolean condition) {
-        return condition ? Collections.singleton(this) : Collections.emptySet();
-    }
-
-    /**
      * Compares this {@code Person} with the given object for equality.
      *
      * @param  obj  the object to compare with this {@code Person}.
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java
index ff5cd7d..c29b760 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java
@@ -37,7 +37,7 @@
 import org.apache.sis.util.Version;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -319,7 +319,7 @@
      *         or various {@link RuntimeException}.
      */
     @Override
-    public boolean tryAdvance(final Consumer<? super Feature> action) throws BackingStoreException {
+    public boolean tryAdvance(final Consumer<? super AbstractFeature> action) throws BackingStoreException {
         try {
             return parse(action, false);
         } catch (Exception e) {                 // Many possible exceptions including unchecked ones.
@@ -336,7 +336,7 @@
      *         or various {@link RuntimeException}.
      */
     @Override
-    public void forEachRemaining(final Consumer<? super Feature> action) throws BackingStoreException {
+    public void forEachRemaining(final Consumer<? super AbstractFeature> action) throws BackingStoreException {
         try {
             parse(action, true);
         } catch (Exception e) {                 // Many possible exceptions including unchecked ones.
@@ -360,7 +360,7 @@
      * @throws EOFException if the file seems to be truncated.
      */
     @SuppressWarnings("fallthrough")
-    private boolean parse(final Consumer<? super Feature> action, final boolean all) throws Exception {
+    private boolean parse(final Consumer<? super AbstractFeature> action, final boolean all) throws Exception {
         for (int type = reader.getEventType(); ; type = reader.next()) {
             /*
              * We do not need to check 'reader.hasNext()' in above loop
@@ -368,7 +368,7 @@
              */
             switch (type) {
                 case START_ELEMENT: {
-                    final Feature f;
+                    final AbstractFeature f;
                     switch (isGPX() ? reader.getLocalName() : "") {
                         case Tags.WAY_POINT: f = parseWayPoint(++wayPointId); break;
                         case Tags.ROUTES:    f = parseRoute   (++routeId);    break;
@@ -394,7 +394,7 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseWayPoint(final int index) throws Exception {
+    private AbstractFeature parseWayPoint(final int index) throws Exception {
         assert reader.isStartElement();
         /*
          * Way points might be located in different elements: <wpt>, <rtept> and <trkpt>.
@@ -409,7 +409,7 @@
                     (lat == null) ? Attributes.LATITUDE : Attributes.LONGITUDE, tagName));
         }
         final Types types = ((Store) owner).types;
-        final Feature feature = types.wayPoint.newInstance();
+        final AbstractFeature feature = types.wayPoint.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
         feature.setPropertyValue(AttributeConvention.GEOMETRY, types.geometries.createPoint(parseDouble(lon), parseDouble(lat)));
         List<Link> links = null;
@@ -472,11 +472,11 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseRoute(final int index) throws Exception {
+    private AbstractFeature parseRoute(final int index) throws Exception {
         assert reader.isStartElement() && Tags.ROUTES.equals(reader.getLocalName());
-        final Feature feature = ((Store) owner).types.route.newInstance();
+        final AbstractFeature feature = ((Store) owner).types.route.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
-        List<Feature> wayPoints = null;
+        List<AbstractFeature> wayPoints = null;
         List<Link> links = null;
         while (true) {
             /*
@@ -528,11 +528,11 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseTrackSegment(final int index) throws Exception {
+    private AbstractFeature parseTrackSegment(final int index) throws Exception {
         assert reader.isStartElement() && Tags.TRACK_SEGMENTS.equals(reader.getLocalName());
-        final Feature feature = ((Store) owner).types.trackSegment.newInstance();
+        final AbstractFeature feature = ((Store) owner).types.trackSegment.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
-        List<Feature> wayPoints = null;
+        List<AbstractFeature> wayPoints = null;
         while (true) {
             /*
              * We do not need to check 'reader.hasNext()' in above loop
@@ -571,11 +571,11 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseTrack(final int index) throws Exception {
+    private AbstractFeature parseTrack(final int index) throws Exception {
         assert reader.isStartElement() && Tags.TRACKS.equals(reader.getLocalName());
-        final Feature feature = ((Store) owner).types.track.newInstance();
+        final AbstractFeature feature = ((Store) owner).types.track.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
-        List<Feature> segments = null;
+        List<AbstractFeature> segments = null;
         List<Link> links = null;
         while (true) {
             /*
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 2e353f6..a754226 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
@@ -46,8 +46,8 @@
 import org.apache.sis.metadata.iso.distribution.DefaultFormat;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -77,7 +77,7 @@
     private Reader reader;
 
     /**
-     * The {@link org.opengis.feature.FeatureType} for routes, tracks, way points, <i>etc</i>.
+     * The {@code FeatureType} for routes, tracks, way points, <i>etc</i>.
      * Currently always {@link Types#DEFAULT}, but we use a field for keeping {@code Reader}
      * and {@code Writer} ready to handle profiles or extensions.
      */
@@ -188,7 +188,7 @@
      * @return base type of all GPX types.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return types.parent;
     }
 
@@ -204,7 +204,7 @@
      *             more experience with the API proposed by {@link org.apache.sis.storage.FeatureSet}.
      */
     @Deprecated
-    public FeatureType getFeatureType(final String name) throws IllegalNameException {
+    public DefaultFeatureType getFeatureType(final String name) throws IllegalNameException {
         return types.names.get(this, name);
     }
 
@@ -216,7 +216,7 @@
      * @throws DataStoreException if an error occurred while creating the feature stream.
      */
     @Override
-    public final synchronized Stream<Feature> features(boolean parallel) throws DataStoreException {
+    public final synchronized Stream<AbstractFeature> features(boolean parallel) throws DataStoreException {
         Reader r = reader;
         reader = null;
         if (r == null) try {
@@ -229,7 +229,7 @@
         } catch (Exception e) {
             throw new DataStoreException(e);
         }
-        final Stream<Feature> features = StreamSupport.stream(r, false);
+        final Stream<AbstractFeature> features = StreamSupport.stream(r, false);
         return features.onClose(r);
     }
 
@@ -241,7 +241,7 @@
      * @throws ConcurrentReadException if the {@code features} stream was provided by this data store.
      * @throws DataStoreException if an error occurred while writing the data.
      */
-    public synchronized void write(final Metadata metadata, final Stream<? extends Feature> features) throws DataStoreException {
+    public synchronized void write(final Metadata metadata, final Stream<? extends AbstractFeature> features) throws DataStoreException {
         try {
             /*
              * If we created a reader for reading metadata, we need to close that reader now otherwise the call
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
index 29461d5..0a8605a 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
@@ -48,8 +48,8 @@
 import org.apache.sis.util.iso.DefaultNameFactory;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -66,27 +66,27 @@
     /**
      * The parent of all other feature types.
      */
-    final FeatureType parent;
+    final DefaultFeatureType parent;
 
     /**
      * Way point GPX feature type.
      */
-    final FeatureType wayPoint;
+    final DefaultFeatureType wayPoint;
 
     /**
      * Route GPX feature type.
      */
-    final FeatureType route;
+    final DefaultFeatureType route;
 
     /**
      * Track GPX feature type.
      */
-    final FeatureType track;
+    final DefaultFeatureType track;
 
     /**
      * Track segment GPX feature type.
      */
-    final FeatureType trackSegment;
+    final DefaultFeatureType trackSegment;
 
     /**
      * The list of feature types to be given to GPC metadata objects.
@@ -101,7 +101,7 @@
      *             more experience with the API proposed by {@link org.apache.sis.storage.FeatureSet}.
      */
     @Deprecated
-    final FeatureNaming<FeatureType> names;
+    final FeatureNaming<DefaultFeatureType> names;
 
     /**
      * Accessor to the geometry implementation in use (Java2D, ESRI or JTS).
@@ -223,7 +223,7 @@
          * │ rtept          │ WayPoint       │ gpx:wptType           │   [0 … ∞]    │
          * └────────────────┴────────────────┴───────────────────────┴──────────────┘
          */
-        final AttributeType<?> groupResult = GroupAsPolylineOperation.getResult(geometries);
+        final DefaultAttributeType<?> groupResult = GroupAsPolylineOperation.getResult(geometries);
         GroupAsPolylineOperation groupOp = new GroupAsPolylineOperation(geomInfo, Tags.ROUTE_POINTS, groupResult);
         builder.clear().setSuperTypes(parent).setNameSpace(Tags.PREFIX).setName("Route");
         builder.addProperty(groupOp);
@@ -305,7 +305,7 @@
      * @param  previous  previously created international strings as array of length 2.
      *                   The first element is the designation and the second element is the definition.
      */
-    private static FeatureType create(final FeatureTypeBuilder builder, final Map<String,InternationalString[]> previous) {
+    private static DefaultFeatureType create(final FeatureTypeBuilder builder, final Map<String,InternationalString[]> previous) {
         for (final PropertyTypeBuilder p : builder.properties()) {
             final GenericName name = p.getName();
             if (!AttributeConvention.contains(name)) {
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
index f7a3504..5d46b15 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
@@ -30,8 +30,8 @@
 import org.apache.sis.util.Version;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -78,7 +78,7 @@
 
     /**
      * Writes the XML declaration followed by GPX metadata.
-     * This method shall be invoked exactly once before {@link #write(Feature)}.
+     * This method shall be invoked exactly once before {@code write(Feature)}.
      *
      * @throws Exception if an error occurred while writing to the XML file.
      */
@@ -142,10 +142,10 @@
      * @throws JAXBException if underlying JAXB marshaller encounter an error.
      */
     @Override
-    public void write(final Feature feature) throws DataStoreException, XMLStreamException, JAXBException {
+    public void write(final AbstractFeature feature) throws DataStoreException, XMLStreamException, JAXBException {
         if (feature != null) {
             final Types types = ((Store) owner).types;
-            final FeatureType type = feature.getType();
+            final DefaultFeatureType type = feature.getType();
             if (types.wayPoint.isAssignableFrom(type)) {
                 writeWayPoint(feature, Tags.WAY_POINT);
             } else {
@@ -165,14 +165,14 @@
                 }
                 if (isRoute) {
                     for (Object prop : (Collection<?>) feature.getPropertyValue(Tags.ROUTE_POINTS)) {
-                        writeWayPoint((Feature) prop, Tags.ROUTE_POINTS);
+                        writeWayPoint((AbstractFeature) prop, Tags.ROUTE_POINTS);
                     }
                 } else {
                     for (Object segment : (Collection<?>) feature.getPropertyValue(Tags.TRACK_SEGMENTS)) {
                         if (segment != null) {
                             writer.writeStartElement(Tags.TRACK_SEGMENTS);
-                            for (Object prop : (Collection<?>) ((Feature) segment).getPropertyValue(Tags.TRACK_POINTS)) {
-                                writeWayPoint((Feature) prop, Tags.TRACK_POINTS);
+                            for (Object prop : (Collection<?>) ((AbstractFeature) segment).getPropertyValue(Tags.TRACK_POINTS)) {
+                                writeWayPoint((AbstractFeature) prop, Tags.TRACK_POINTS);
                             }
                             writer.writeEndElement();
                         }
@@ -191,7 +191,7 @@
      * @throws XMLStreamException if underlying STAX writer encounter an error.
      * @throws JAXBException if underlying JAXB marshaller encounter an error.
      */
-    private void writeWayPoint(final Feature feature, final String tagName) throws XMLStreamException, JAXBException {
+    private void writeWayPoint(final AbstractFeature feature, final String tagName) throws XMLStreamException, JAXBException {
         if (feature != null) {
             final double[] pt = Geometries.wrap(feature.getPropertyValue(AttributeConvention.GEOMETRY))
                                            .map(GeometryWrapper::getPointCoordinates).orElse(null);
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
index cc12760..d344b96 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
@@ -49,7 +49,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -107,7 +107,7 @@
  * @since   0.8
  * @module
  */
-public abstract class StaxStreamReader extends StaxStreamIO implements XMLStreamConstants, Spliterator<Feature>, Runnable {
+public abstract class StaxStreamReader extends StaxStreamIO implements XMLStreamConstants, Spliterator<AbstractFeature>, Runnable {
     /**
      * The XML stream reader.
      */
@@ -168,7 +168,7 @@
      *         or various {@link RuntimeException} among others.
      */
     @Override
-    public abstract boolean tryAdvance(Consumer<? super Feature> action) throws BackingStoreException;
+    public abstract boolean tryAdvance(Consumer<? super AbstractFeature> action) throws BackingStoreException;
 
     /**
      * Returns {@code null} by default since non-binary XML files are hard to split.
@@ -176,7 +176,7 @@
      * @return {@code null}.
      */
     @Override
-    public Spliterator<Feature> trySplit() {
+    public Spliterator<AbstractFeature> trySplit() {
         return null;
     }
 
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java
index ea86c85..6638e32 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java
@@ -34,7 +34,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -89,7 +89,7 @@
  * @since   0.8
  * @module
  */
-public abstract class StaxStreamWriter extends StaxStreamIO implements Consumer<Feature> {
+public abstract class StaxStreamWriter extends StaxStreamIO implements Consumer<AbstractFeature> {
     /**
      * The XML stream writer.
      */
@@ -169,16 +169,16 @@
      *         but also {@link JAXBException} if JAXB is used for marshalling metadata objects,
      *         {@link DataStoreException}, {@link ClassCastException}, <i>etc.</i>
      */
-    public abstract void write(Feature feature) throws Exception;
+    public abstract void write(AbstractFeature feature) throws Exception;
 
     /**
-     * Delegates to {@link #write(Feature)}, wrapping {@code Exception} into unchecked {@code BackingStoreException}.
+     * Delegates to {@code write(Feature)}, wrapping {@code Exception} into unchecked {@code BackingStoreException}.
      *
      * @param  feature  the feature to write.
      * @throws BackingStoreException if an error occurred while writing to the XML file.
      */
     @Override
-    public void accept(final Feature feature) throws BackingStoreException {
+    public void accept(final AbstractFeature feature) throws BackingStoreException {
         try {
             write(feature);
         } catch (BackingStoreException e) {
diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/MetadataTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/MetadataTest.java
index 567bd76..27840c9 100644
--- a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/MetadataTest.java
+++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/MetadataTest.java
@@ -23,6 +23,7 @@
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
+import org.junit.Ignore;
 
 import static org.junit.Assert.*;
 import static org.apache.sis.test.TestUtilities.date;
@@ -62,6 +63,11 @@
      */
     @Test
     @DependsOnMethod("testEqualsAndHashCode")
+    @Ignore("Can not execute this test on this branch because it depends on Citation.getOnlineResources() "
+          + "and Identification.getExtents() methods, which are not present in GeoAPI 3.0 interfaces. "
+          + "Despite this test failure, the copy constructor should nevertheless works in practice "
+          + "if the Citation and Identification objects are instances of DefaultCitation or AbstractExtent "
+          + "(the SIS implementations of GeoAPI interfaces).")
     public void testCopyConstructor() throws URISyntaxException {
         final Metadata md1 = create();
         final Metadata md2 = new Metadata(md1, null);
diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/ReaderTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/ReaderTest.java
index da119f8..74d7d33 100644
--- a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/ReaderTest.java
+++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/ReaderTest.java
@@ -42,8 +42,8 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.metadata.content.FeatureTypeInfo;
+import org.apache.sis.feature.AbstractFeature;
+import org.opengis.util.GenericName;
 
 
 /**
@@ -209,10 +209,10 @@
          */
         final FeatureCatalogueDescription content = (FeatureCatalogueDescription) getSingleton(md.getContentInfo());
         assertTrue("isIncludedWithDataset", content.isIncludedWithDataset());
-        final Iterator<? extends FeatureTypeInfo> it = content.getFeatureTypeInfo().iterator();
-        assertStringEquals("Route",    it.next().getFeatureTypeName().tip());
-        assertStringEquals("Track",    it.next().getFeatureTypeName().tip());
-        assertStringEquals("WayPoint", it.next().getFeatureTypeName().tip());
+        final Iterator<? extends GenericName> it = content.getFeatureTypes().iterator();
+        assertStringEquals("Route",    it.next().tip());
+        assertStringEquals("Track",    it.next().tip());
+        assertStringEquals("WayPoint", it.next().tip());
         assertFalse("hasNext", it.hasNext());
     }
 
@@ -227,8 +227,8 @@
         try (Store reader = create("1.0/waypoint.xml")) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals("version", StoreProvider.V1_0, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyPoint(it.next(), 0, false);
                 verifyPoint(it.next(), 1, false);
                 verifyPoint(it.next(), 2, false);
@@ -248,8 +248,8 @@
         try (Store reader = create("1.1/waypoint.xml")) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals("version", StoreProvider.V1_1, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyPoint(it.next(), 0, true);
                 verifyPoint(it.next(), 1, true);
                 verifyPoint(it.next(), 2, true);
@@ -269,8 +269,8 @@
         try (Store reader = create("1.0/route.xml")) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals("version", StoreProvider.V1_0, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyRoute(it.next(), false, 1);
                 verifyEmpty(it.next(), "rtept");
                 assertFalse("hasNext", it.hasNext());
@@ -298,8 +298,8 @@
      * This verification is shared by {@link #testRoute110()} and {@link #testSequentialReads()}.
      */
     static void verifyRoute110(final Store reader) throws DataStoreException {
-        try (Stream<Feature> features = reader.features(false)) {
-            final Iterator<Feature> it = features.iterator();
+        try (Stream<AbstractFeature> features = reader.features(false)) {
+            final Iterator<AbstractFeature> it = features.iterator();
             verifyRoute(it.next(), true, 3);
             verifyEmpty(it.next(), "rtept");
             assertFalse("hasNext", it.hasNext());
@@ -314,7 +314,7 @@
      * @param  numLinks  expected number of links.
      */
     @SuppressWarnings("fallthrough")
-    private static void verifyRoute(final Feature f, final boolean v11, final int numLinks) {
+    private static void verifyRoute(final AbstractFeature f, final boolean v11, final int numLinks) {
         assertEquals("name",       "Route name",          f.getPropertyValue("name"));
         assertEquals("cmt",        "Route comment",       f.getPropertyValue("cmt"));
         assertEquals("desc",       "Route description",   f.getPropertyValue("desc"));
@@ -334,9 +334,9 @@
 
         final List<?> points = (List<?>) f.getPropertyValue("rtept");
         assertEquals("points.size()", 3, points.size());
-        verifyPoint((Feature) points.get(0), 0, v11);
-        verifyPoint((Feature) points.get(1), 1, v11);
-        verifyPoint((Feature) points.get(2), 2, v11);
+        verifyPoint((AbstractFeature) points.get(0), 0, v11);
+        verifyPoint((AbstractFeature) points.get(1), 1, v11);
+        verifyPoint((AbstractFeature) points.get(2), 2, v11);
 
         final Polyline p = (Polyline) f.getPropertyValue("sis:geometry");
         assertEquals("pointCount", 3, p.getPointCount());
@@ -352,7 +352,7 @@
      * @param  f    the route or track to verify.
      * @param  dep  {@code "rtept"} if verifying a route, or {@code "trkseg"} if verifying a track.
      */
-    private static void verifyEmpty(final Feature f, final String dep) {
+    private static void verifyEmpty(final AbstractFeature f, final String dep) {
         assertNull("name",   f.getPropertyValue("name"));
         assertNull("cmt",    f.getPropertyValue("cmt"));
         assertNull("desc",   f.getPropertyValue("desc"));
@@ -376,8 +376,8 @@
         try (Store reader = create("1.0/track.xml")) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals("version", StoreProvider.V1_0, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyTrack(it.next(), false, 1);
                 verifyEmpty(it.next(), "trkseg");
                 assertFalse("hasNext", it.hasNext());
@@ -396,8 +396,8 @@
         try (Store reader = create("1.1/track.xml")) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals("version", StoreProvider.V1_1, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyTrack(it.next(), true, 3);
                 verifyEmpty(it.next(), "trkseg");
                 assertFalse("hasNext", it.hasNext());
@@ -413,7 +413,7 @@
      * @param  numLinks  expected number of links.
      */
     @SuppressWarnings("fallthrough")
-    private static void verifyTrack(final Feature f, final boolean v11, final int numLinks) {
+    private static void verifyTrack(final AbstractFeature f, final boolean v11, final int numLinks) {
         assertEquals("name",       "Track name",          f.getPropertyValue("name"));
         assertEquals("cmt",        "Track comment",       f.getPropertyValue("cmt"));
         assertEquals("desc",       "Track description",   f.getPropertyValue("desc"));
@@ -433,13 +433,13 @@
 
         final List<?> segments = (List<?>) f.getPropertyValue("trkseg");
         assertEquals("segments.size()", 2, segments.size());
-        final Feature seg1 = (Feature) segments.get(0);
-        final Feature seg2 = (Feature) segments.get(1);
+        final AbstractFeature seg1 = (AbstractFeature) segments.get(0);
+        final AbstractFeature seg2 = (AbstractFeature) segments.get(1);
         final List<?> points = (List<?>) seg1.getPropertyValue("trkpt");
         assertEquals("points.size()", 3, points.size());
-        verifyPoint((Feature) points.get(0), 0, v11);
-        verifyPoint((Feature) points.get(1), 1, v11);
-        verifyPoint((Feature) points.get(2), 2, v11);
+        verifyPoint((AbstractFeature) points.get(0), 0, v11);
+        verifyPoint((AbstractFeature) points.get(1), 1, v11);
+        verifyPoint((AbstractFeature) points.get(2), 2, v11);
         assertTrue(((Collection<?>) seg2.getPropertyValue("trkpt")).isEmpty());
 
         final Polyline p = (Polyline) f.getPropertyValue("sis:geometry");
@@ -457,7 +457,7 @@
      * @param  index  index of the point being verified: 0, 1 or 2.
      * @param  v11    {@code true} for GPX 1.1, or {@code false} for GPX 1.0.
      */
-    private static void verifyPoint(final Feature f, final int index, final boolean v11) {
+    private static void verifyPoint(final AbstractFeature f, final int index, final boolean v11) {
         assertEquals("sis:identifier", index + 1, f.getPropertyValue("sis:identifier"));
         switch (index) {
             case 0: {
@@ -614,14 +614,14 @@
     @DependsOnMethod("testSequentialReads")
     public void testConcurrentReads() throws DataStoreException {
         try (Store reader = createFromURL()) {
-            final Stream<Feature>   f1 = reader.features(false);
-            final Iterator<Feature> i1 = f1.iterator();
+            final Stream<AbstractFeature>   f1 = reader.features(false);
+            final Iterator<AbstractFeature> i1 = f1.iterator();
             verifyRoute(i1.next(), true, 3);
-            final Stream<Feature>   f2 = reader.features(false);
-            final Iterator<Feature> i2 = f2.iterator();
+            final Stream<AbstractFeature>   f2 = reader.features(false);
+            final Iterator<AbstractFeature> i2 = f2.iterator();
             verifyEmpty(i1.next(), "rtept");
-            final Stream<Feature>   f3 = reader.features(false);
-            final Iterator<Feature> i3 = f3.iterator();
+            final Stream<AbstractFeature>   f3 = reader.features(false);
+            final Iterator<AbstractFeature> i3 = f3.iterator();
             verifyRoute(i2.next(), true, 3);
             verifyRoute(i3.next(), true, 3);
             verifyEmpty(i3.next(), "rtept");
diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/TypesTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/TypesTest.java
index 411ebff..20db914 100644
--- a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/TypesTest.java
+++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/TypesTest.java
@@ -27,8 +27,8 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -58,8 +58,8 @@
     /**
      * Verifies that all designations and definitions can be read from the resources.
      */
-    private static void testResources(final FeatureType type) {
-        for (final PropertyType p : type.getProperties(false)) {
+    private static void testResources(final DefaultFeatureType type) {
+        for (final AbstractIdentifiedType p : type.getProperties(false)) {
             final GenericName name = p.getName();
             if (!AttributeConvention.contains(name)) {
                 final String label = name.toString();
diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java
index ae66a25..ff8b968 100644
--- a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java
+++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java
@@ -41,7 +41,7 @@
 import static org.apache.sis.test.MetadataAssert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -253,7 +253,7 @@
          * Way Points as defined in "waypoint.xml" test file.
          * Appear also in "route.xml" and "track.xml" files.
          */
-        final Feature point1 = types.wayPoint.newInstance();
+        final AbstractFeature point1 = types.wayPoint.newInstance();
         point1.setPropertyValue("sis:geometry",  new Point(15, 10));
         point1.setPropertyValue("time",          Instant.parse("2010-01-10T00:00:00Z"));
         point1.setPropertyValue("name",          "first point");
@@ -275,7 +275,7 @@
         point1.setPropertyValue("link",          Arrays.asList(new Link(new URI("http://first-address1.org")),
                                                                new Link(new URI("http://first-address2.org")),
                                                                new Link(new URI("http://first-address3.org"))));
-        final Feature point3 = types.wayPoint.newInstance();
+        final AbstractFeature point3 = types.wayPoint.newInstance();
         point3.setPropertyValue("sis:geometry",  new Point(35, 30));
         point3.setPropertyValue("time",          Instant.parse("2010-01-30T00:00:00Z"));
         point3.setPropertyValue("name",          "third point");
@@ -296,17 +296,17 @@
         point3.setPropertyValue("fix",           Fix.THREE_DIMENSIONAL);
         point3.setPropertyValue("link",          Arrays.asList(new Link(new URI("http://third-address1.org")),
                                                                new Link(new URI("http://third-address2.org"))));
-        final Feature point2 = types.wayPoint.newInstance();
+        final AbstractFeature point2 = types.wayPoint.newInstance();
         point2.setPropertyValue("sis:geometry", new Point(25, 20));
-        final List<Feature> wayPoints = Arrays.asList(point1, point2, point3);
-        final List<Feature> features;
+        final List<AbstractFeature> wayPoints = Arrays.asList(point1, point2, point3);
+        final List<AbstractFeature> features;
         switch (type) {
             case WAY_POINT: {
                 features = wayPoints;
                 break;
             }
             case ROUTE: {
-                final Feature route1 = types.route.newInstance();
+                final AbstractFeature route1 = types.route.newInstance();
                 route1.setPropertyValue("name",   "Route name");
                 route1.setPropertyValue("cmt",    "Route comment");
                 route1.setPropertyValue("desc",   "Route description");
@@ -317,16 +317,16 @@
                 route1.setPropertyValue("link",   Arrays.asList(new Link(new URI("http://route-address1.org")),
                                                                 new Link(new URI("http://route-address2.org")),
                                                                 new Link(new URI("http://route-address3.org"))));
-                final Feature route2 = types.route.newInstance();
+                final AbstractFeature route2 = types.route.newInstance();
                 features = Arrays.asList(route1, route2);
                 break;
             }
             case TRACK: {
-                final Feature seg1 = types.trackSegment.newInstance();
-                final Feature seg2 = types.trackSegment.newInstance();
+                final AbstractFeature seg1 = types.trackSegment.newInstance();
+                final AbstractFeature seg2 = types.trackSegment.newInstance();
                 seg1.setPropertyValue("trkpt", wayPoints);
 
-                final Feature track1 = types.track.newInstance();
+                final AbstractFeature track1 = types.track.newInstance();
                 track1.setPropertyValue("name",   "Track name");
                 track1.setPropertyValue("cmt",    "Track comment");
                 track1.setPropertyValue("desc",   "Track description");
@@ -337,7 +337,7 @@
                 track1.setPropertyValue("link",   Arrays.asList(new Link(new URI("http://track-address1.org")),
                                                                 new Link(new URI("http://track-address2.org")),
                                                                 new Link(new URI("http://track-address3.org"))));
-                final Feature track2 = types.track.newInstance();
+                final AbstractFeature track2 = types.track.newInstance();
                 features = Arrays.asList(track1, track2);
                 break;
             }