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

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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new bc588ec340 Provide a way to search for dimensions of a sub-CRS inside 
a CRS with tolerance for different datum. This is needed for a more robust fix 
of netCDF handling of CRS, when the two-dimensional component of a 
n-dimensional CRS guessed from netCDF dimensions need to be replaced by the 
grid mapping CRS.
bc588ec340 is described below

commit bc588ec340e24222254bb0bfa8f5b1e4163f53b0
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Oct 27 18:35:16 2026 +0100

    Provide a way to search for dimensions of a sub-CRS inside a CRS with 
tolerance for different datum.
    This is needed for a more robust fix of netCDF handling of CRS, when the 
two-dimensional component
    of a n-dimensional CRS guessed from netCDF dimensions need to be replaced 
by the grid mapping CRS.
---
 .../main/org/apache/sis/referencing/CRS.java       | 327 ++++++++++++++++-----
 .../referencing/operation/SubOperationInfo.java    |  32 +-
 .../org/apache/sis/referencing/package-info.java   |   2 +-
 .../test/org/apache/sis/referencing/CRSTest.java   |  70 ++++-
 .../sis/storage/netcdf/base/GridMapping.java       |  51 ++--
 .../main/org/apache/sis/util/Classes.java          |  22 +-
 .../main/org/apache/sis/util/ComparisonMode.java   |   8 +-
 .../main/org/apache/sis/util/Utilities.java        |   2 +
 8 files changed, 380 insertions(+), 134 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
index 195baa5cb3..75e33b9e7f 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
@@ -19,9 +19,13 @@ package org.apache.sis.referencing;
 import java.util.Map;
 import java.util.List;
 import java.util.ArrayList;
+import java.util.Set;
 import java.util.BitSet;
+import java.util.HashSet;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.function.BiPredicate;
 import java.util.logging.Filter;
 import java.util.logging.Logger;
 import java.util.logging.LogRecord;
@@ -95,6 +99,7 @@ import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.OptionalCandidate;
 import org.apache.sis.util.Utilities;
+import org.apache.sis.util.Classes;
 import org.apache.sis.util.internal.shared.Constants;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
@@ -102,6 +107,7 @@ import org.apache.sis.util.logging.Logging;
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.referencing.ObjectDomain;
 import org.opengis.referencing.crs.DerivedCRS;
+import org.opengis.referencing.datum.DatumEnsemble;
 import org.opengis.referencing.datum.DynamicReferenceFrame;
 import org.opengis.coordinate.CoordinateMetadata;
 
@@ -116,12 +122,12 @@ import org.opengis.coordinate.CoordinateMetadata;
  *   <li>Finding coordinate operations between a source and a target 
<abbr>CRS</abbr>.</li>
  * </ul>
  *
- * <h2>Usage example</h2>
+ * <h2>Getting a <abbr>CRS</abbr> and a coordinate operation</h2>
  * The most frequently used methods in this class are {@link #forCode 
forCode(…)}, {@link #fromWKT fromWKT(…)}
  * and {@link #findOperation(CoordinateReferenceSystem, 
CoordinateReferenceSystem, GeographicBoundingBox) findOperation(…)}.
  * An usage example is like below
- * (see the <a 
href="https://sis.apache.org/tables/CoordinateReferenceSystems.html";>Apache 
<abbr>SIS</abbr>™
- * Coordinate Reference System (<abbr>CRS</abbr>) codes</a> page for the 
complete list of EPSG codes):
+ * (see the <a 
href="https://sis.apache.org/tables/CoordinateReferenceSystems.html";>Coordinate 
Reference Systems</a>
+ * page for the complete list of <abbr>EPSG</abbr> codes):
  *
  * {@snippet lang="java" :
  *     CoordinateReferenceSystem source = CRS.forCode("EPSG:4326");            
       // WGS 84
@@ -137,7 +143,7 @@ import org.opengis.coordinate.CoordinateMetadata;
  *     System.out.println(position);
  *     }
  *
- * <h2>Note on kinds of <abbr>CRS</abbr></h2>
+ * <h2>Identifying the kind of <abbr>CRS</abbr></h2>
  * The {@link #getSingleComponents(CoordinateReferenceSystem)} method 
decomposes an arbitrary CRS into a flat
  * list of single components. In such flat list, vertical and temporal 
components can easily be identified by
  * {@code instanceof} checks. But identifying the horizontal component is not 
as easy. The list below suggests
@@ -225,7 +231,7 @@ public final class CRS {
      * If the EPSG geodetic dataset has been used, the {@linkplain 
NamedIdentifier#getAuthority() authority} title
      * will be something like <q>EPSG geodetic dataset</q>, otherwise it will 
be <q>Subset of EPSG</q>.
      *
-     * <h4>Extended set of codes</h4>
+     * <h4>Extended set of <abbr>CRS</abbr> authority codes</h4>
      * If this method is invoked during the parsing of a GML document,
      * then the set of known codes temporarily includes the {@code 
<gml:identifier>} values
      * of all {@link CoordinateReferenceSystem} definitions which have been 
parsed before this method call.
@@ -234,8 +240,8 @@ public final class CRS {
      * This feature allows embedded or linked data to references a CRS 
definition
      * in the same file or a file included by an {@code xlink:href} attribute.
      *
-     * <h4>URI forms</h4>
-     * This method accepts also the URN and URL syntaxes.
+     * <h4><abbr>URI</abbr> forms of <abbr>CRS</abbr> authority codes</h4>
+     * This method accepts also the <abbr>URN</abbr> and <abbr>URL</abbr> 
syntaxes.
      * For example, the following codes are considered equivalent to {@code 
"EPSG:4326"}:
      * <ul>
      *   <li>{@code "EPSG::4326"}</li>
@@ -244,8 +250,8 @@ public final class CRS {
      *   <li>{@code "http://www.opengis.net/gml/srs/epsg.xml#4326"}</li>
      * </ul>
      *
-     * URIs can be combined for creating larger objects. For example, the 
following URIs combine a
-     * two-dimensional WGS84 reference system (EPSG:4326) with a Mean Sea 
Level height (EPSG:5714).
+     * <abbr>URI</abbr>s can be combined for creating larger objects. For 
example, the following <abbr>URI</abbr>s
+     * combine a two-dimensional WGS84 reference system (EPSG:4326) with a 
Mean Sea Level height (EPSG:5714).
      * The result is a three-dimensional {@linkplain 
org.apache.sis.referencing.crs.DefaultCompoundCRS
      * compound coordinate reference system}:
      *
@@ -256,7 +262,7 @@ public final class CRS {
      *            2=http://www.opengis.net/def/crs/epsg/0/5714";</code></li>
      * </ul>
      *
-     * <p>URNs (but not URLs) can also combine a
+     * <p><abbr>URN</abbr>s (but not <abbr>URL</abbr>s) can also combine a
      * {@linkplain org.apache.sis.referencing.datum.DefaultGeodeticDatum 
geodetic reference frame} with an
      * {@linkplain org.apache.sis.referencing.cs.DefaultEllipsoidalCS 
ellipsoidal coordinate system} for creating a new
      * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS 
geographic CRS}, or a base geographic CRS with a
@@ -264,9 +270,9 @@ public final class CRS {
      * {@linkplain org.apache.sis.referencing.cs.DefaultCartesianCS Cartesian 
coordinate system} for creating a new
      * {@linkplain org.apache.sis.referencing.crs.DefaultProjectedCRS 
projected coordinate reference system}.</p>
      *
-     * <h4>Reverse operation</h4>
-     * The {@link IdentifiedObjects#lookupURN(IdentifiedObject, Citation)} 
method can be seen
-     * as a converse of this method: from a CRS object, it tries to find a URN 
that describes it.
+     * <h4>Reverse of <abbr>CRS</abbr> from authority codes</h4>
+     * The {@link IdentifiedObjects#lookupURN(IdentifiedObject, Citation)} 
method can be seen as a converse of
+     * this method: from a <abbr>CRS</abbr> object, it tries to find a 
<abbr>URN</abbr> that identifies it.
      * More codes may also be supported depending on which extension modules 
are available.
      *
      * @param  code  the authority code.
@@ -497,38 +503,37 @@ public final class CRS {
     /**
      * Returns whether the given Coordinate Reference Systems can be 
considered as equivalent.
      * Two <abbr>CRS</abbr>s are considered equivalent if they may have some 
differences in metadata or data structure,
-     * but replacing one <abbr>CRS</abbr> by the other should not change the 
input and output coordinate values in operations.
-     *
-     * <h4>Implementation note</h4>
-     * This is a convenience method for the following method call:
+     * but replacing one <abbr>CRS</abbr> by the other does not change the 
input and output coordinate values in operations.
+     * Invoking this method is equivalent to the following method call:
      *
      * {@snippet lang="java" :
-     *     return deepEquals(object1, object2, ComparisonMode.IGNORE_METADATA);
+     *     return deepEquals(crs1, crs2, ComparisonMode.COMPATIBILITY);
      *     }
      *
-     * @param  object1  the first object to compare (may be null).
-     * @param  object2  the second object to compare (may be null).
-     * @return {@code true} if both objects are equivalent.
+     * @param  crs1  the first <abbr>CRS</abbr> to compare (can be null).
+     * @param  crs2  the second <abbr>CRS</abbr> to compare (can be null).
+     * @return {@code true} if both <abbr>CRS</abbr>s are equivalent for 
coordinate operation purposes.
      *
      * @see ComparisonMode#COMPATIBILITY
+     * @see EquivalenceProperty#COMPONENT
      * @see Utilities#equalsIgnoreMetadata(Object, Object)
      *
      * @since 1.5
      */
-    public static boolean equivalent(final CoordinateReferenceSystem object1, 
final CoordinateReferenceSystem object2) {
-        if (object1 == object2) {
+    public static boolean equivalent(final CoordinateReferenceSystem crs1, 
final CoordinateReferenceSystem crs2) {
+        if (crs1 == crs2) {
             return true;
         }
-        if (object1 == null || object2 == null) {
+        if (crs1 == null || crs2 == null) {
             return false;
         }
-        if (object1 instanceof LenientComparable) {
-            return ((LenientComparable) object1).equals(object2, 
ComparisonMode.COMPATIBILITY);
+        if (crs1 instanceof LenientComparable) {
+            return ((LenientComparable) crs1).equals(crs2, 
ComparisonMode.COMPATIBILITY);
         }
-        if (object2 instanceof LenientComparable) {
-            return ((LenientComparable) object2).equals(object1, 
ComparisonMode.COMPATIBILITY);
+        if (crs2 instanceof LenientComparable) {
+            return ((LenientComparable) crs2).equals(crs1, 
ComparisonMode.COMPATIBILITY);
         }
-        return object1.equals(object2);
+        return crs1.equals(crs2);
     }
 
     /**
@@ -709,14 +714,14 @@ public final class CRS {
      * If there is no known operation between the given pair of 
<abbr>CRS</abbr>s,
      * then this method throws an {@link OperationNotFoundException}.</p>
      *
-     * <h4>Inverse operation</h4>
+     * <h4>Equivalence of inverse coordinate operation</h4>
      * Note that <code>CRS.findOperation(<var>B</var>, <var>A</var>, 
<var>aoi</var>)</code> is not necessarily
      * the exact converse of <code>CRS.findOperation(<var>A</var>, 
<var>B</var>, <var>aoi</var>)</code>.
      * Some deviations may exist, for example because of different paths 
explored in the geodetic database.
      * For the mathematical inverse of an existing {@link 
CoordinateOperation}, using
      * {@link org.opengis.referencing.operation.MathTransform#inverse()} is 
preferable.
      *
-     * <h4>Advanced usages</h4>
+     * <h4>Using coordinate operations with more complex objects</h4>
      * It is not recommended to use the returned coordinate operation for 
transforming bounding boxes simply by
      * transforming the four corners. Use the {@linkplain 
Envelopes#transform(Envelope, CoordinateReferenceSystem)
      * envelope transformation methods} instead. These methods will take care 
of line curvature, envelope spanning
@@ -774,7 +779,7 @@ public final class CRS {
      * If there is no known operation between the given pair of 
<abbr>CRS</abbr>s,
      * then this method throws an {@link OperationNotFoundException}.</p>
      *
-     * <h4>Inverse operation</h4>
+     * <h4>Equivalence of inverse coordinate operation</h4>
      * Note that <code>CRS.findOperation(<var>B</var>, <var>A</var>, 
<var>context</var>)</code> is not necessarily
      * the exact converse of <code>CRS.findOperation(<var>A</var>, 
<var>B</var>, <var>context</var>)</code>.
      * Some deviations may exist, for example because of different paths 
explored in the geodetic database.
@@ -1094,7 +1099,7 @@ public final class CRS {
      *
      * @param  crs  the coordinate reference frame from which to get the 
epoch, or {@code null}.
      * @return epoch to which the coordinates of stations defining the dynamic 
CRS frame are referenced.
-     * @throws GeodeticException if some CRS components haave different epochs.
+     * @throws GeodeticException if some <abbr>CRS</abbr> components have 
different epochs.
      *
      * @since 1.5
      */
@@ -1188,15 +1193,15 @@ public final class CRS {
      * {@linkplain org.apache.sis.metadata.iso.extent.DefaultExtent#intersect 
intersection}
      * of the domain of validity of all components.
      *
-     * <h4>Ellipsoidal height</h4>
+     * <h4>Augmenting a two-dimensional <abbr>CRS</abbr> with an ellipsoidal 
height</h4>
      * If a two-dimensional geographic or projected CRS is followed or 
preceded by a vertical CRS with ellipsoidal
      * {@linkplain 
org.apache.sis.referencing.datum.DefaultVerticalDatum#getRealizationMethod() 
realization method},
-     * this method combines them in a single three-dimensional geographic or 
projected CRS.  Note that standalone
-     * ellipsoidal heights are not allowed according ISO 19111. But if such 
situation is nevertheless found, then
-     * the action described here fixes the issue. This is the reverse of 
<code>{@linkplain #getVerticalComponent
-     * getVerticalComponent}(crs, true)</code>.
+     * this method combines them in a single three-dimensional geographic or 
projected <abbr>CRS</abbr>.
+     * Note that standalone ellipsoidal heights are not allowed according 
<abbr>ISO</abbr> 19111.
+     * But if such situation is nevertheless found, then the action described 
here fixes the standard violation.
+     * This is the reverse of <code>{@linkplain #getVerticalComponent 
getVerticalComponent}(crs, true)</code>.
      *
-     * <h4>Components order</h4>
+     * <h4>{@code SingleCRS} component order in {@code CompoundCRS}</h4>
      * Apache SIS is permissive on the order of components that can be used in 
a compound CRS.
      * However for better inter-operability, users are encouraged to follow 
the order mandated by ISO 19162:
      *
@@ -1228,16 +1233,188 @@ public final class CRS {
     }
 
     /**
-     * Returns a mask of the dimensions where a {@code subCRS} element is 
found in the given <abbr>CRS</abbr>.
-     * If the given {@code crs} is {@linkplain #equivalent equivalent} to an 
element of the {@code subCRS} array,
-     * then this method returns a {@link BitSet} in which all bits are set to 
{@code true} in the range from 0 to
-     * <var>n</var>−1, where <var>n</var> is the number of dimensions of 
{@code crs}.
-     * Otherwise, if {@code crs} is an instance of {@link CompoundCRS}, then 
the {@code crs} components are traversed
-     * recursively and compared in the same way as described above, with bit 
shifted at the position corresponding to
-     * each {@code crs} component. If no match is found, then the returned 
{@link BitSet} is empty.
+     * Identification of a property that defines when two <abbr>CRS</abbr>s 
are considered equivalent.
+     * A comparison between two {@link SingleCRS} instances can take in 
account all properties relevant
+     * to coordinate operations, or only the datum, or only the datum 
<em>type</em>.
+     * These properties are compared in {@linkplain 
ComparisonMode#COMPATIBILITY compatibility mode}.
      *
-     * <h4>Example</h4>
-     * The following snippet gets the dimensions of the temporal and vertical 
components of a <abbr>CRS</abbr>:
+     * <p>Enumeration values are ordered from strictest criterion to most 
lenient criterion.</p>
+     *
+     * @author  Martin Desruisseaux (Geomatys)
+     * @version 1.8
+     *
+     * @see ComparisonMode#COMPATIBILITY
+     *
+     * @since 1.8
+     */
+    public enum EquivalenceProperty implements BiPredicate<SingleCRS, 
SingleCRS> {
+        /**
+         * Compares all {@code SingleCRS} properties relevant to coordinate 
operations.
+         * Invoking <code>COMPONENT.{@link #test test}(<var>a</var>, 
<var>b</var>)</code> is equivalent
+         * to invoking <code>{@link #equivalent(CoordinateReferenceSystem, 
CoordinateReferenceSystem)
+         * CRS.equivalent}(<var>a</var>, <var>b</var>)</code>.
+         *
+         * @see #equivalent(CoordinateReferenceSystem, 
CoordinateReferenceSystem)
+         */
+        COMPONENT,
+
+        /**
+         * Compares only the {@code SingleCRS} datum properties; coordinate 
systems are ignored.
+         * Therefore, the <abbr>CRS</abbr> comparisons are insensitive to 
coordinate system type,
+         * axis order, axis directions and axis units of measurements.
+         * Datum properties are compared in {@linkplain 
ComparisonMode#COMPATIBILITY compatibility mode}.
+         *
+         * <p>The type of a <abbr>CRS</abbr> is closely related to the type of 
its datum.
+         * Since this criterion matches <abbr>CRS</abbr> with the same datum,
+         * it indirectly requires the matched <abbr>CRS</abbr>s to be of the 
same type ignoring specializations
+         * (<i>e.g.</i>, the difference between {@link GeographicCRS} and 
geocentric <abbr>CRS</abbr> is ignored).
+         * Furthermore, a {@link DerivedCRS} is considered equivalent to its 
base <abbr>CRS</abbr>.</p>
+         *
+         * @see SingleCRS#getDatum()
+         */
+        DATUM {
+            @Override Predicate<SingleCRS> comparator(final SingleCRS crs) {
+                final IdentifiedObject datum = DatumOrEnsemble.of(crs);
+                return (other) -> Utilities.deepEquals(datum, 
DatumOrEnsemble.of(other), ComparisonMode.COMPATIBILITY);
+            }
+        },
+
+        /**
+         * Compares only the {@code SingleCRS} datum <em>types</em>.
+         * Not only coordinate systems are ignored, but datum changes are also 
ignored.
+         * Datum ensembles are considered equivalent to ordinary datums,
+         * but dynamic datums are not considered equivalent to static datum
+         * (because dynamic datums require more information: a data epoch).
+         *
+         * <p><b>Example:</b>
+         * <q><abbr>WGS</abbr> 1972</q> and <q><abbr>WGS</abbr> 1984</q> are 
considered equivalent
+         * even if they are different datums requiring a coordinate 
transformation between them.</p>
+         */
+        DATUM_TYPE {
+            @Override Predicate<SingleCRS> comparator(final SingleCRS crs) {
+                final Set<Class<? extends Datum>> types = getDatumType(crs);
+                return (other) -> types.equals(getDatumType(other));
+            }
+        };
+
+        /**
+         * Returns the datum types implemented by the datum of the given 
<abbr>CRS</abbr>.
+         * The return value should contain exactly one element, but a 
collection is used for robustness.
+         * If the {@code crs} is {@code null} or has no datum, an empty set is 
returned (not {@code null}).
+         *
+         * @param  crs  the <abbr>CRS</abbr> component from which to get the 
datum type, or {@code null}.
+         * @return the datum type(s) of the given <abbr>CRS</abbr> component, 
or an empty set if none.
+         */
+        private static Set<Class<? extends Datum>> getDatumType(final 
SingleCRS crs) {
+            final var types = new HashSet<Class<? extends Datum>>(4);
+            for (Class<? extends Datum> type : 
Classes.getLeafInterfaces(Classes.getClass(DatumOrEnsemble.of(crs)), 
Datum.class)) {
+                if (!DatumEnsemble.class.isAssignableFrom(type)) {
+                    types.add(type);
+                }
+            }
+            return types;
+        }
+
+        /**
+         * Creates a comparator for comparing the given <abbr>CRS</abbr> 
component with other components.
+         * The {@code crs} argument may be {@code null} and the returned 
predicate must accept {@code null}.
+         *
+         * @param  crs  the <abbr>CRS</abbr> component to compare, or {@code 
null}.
+         * @return a predicate for comparing the given component with other 
components.
+         */
+        Predicate<SingleCRS> comparator(final SingleCRS crs) {
+            return (other) -> equivalent(crs, other);
+        }
+
+        /**
+         * Compares two <abbr>CRS</abbr> components for equivalence.
+         * If this method is invoked on the {@link #COMPONENT} enumeration 
value,
+         * replacing one <abbr>CRS</abbr> by the other does not change the 
result of coordinate operations.
+         * When invoked on other enumeration values, more lenient comparisons 
are performed which can accept
+         * changes in coordinate system types, axis orders, directions, units 
of measurement or datum properties.
+         *
+         * @param  crs1  the first <abbr>CRS</abbr> to compare (can be null).
+         * @param  crs2  the second <abbr>CRS</abbr> to compare (can be null).
+         * @return whether the properties identified by this enumeration value 
are equivalent.
+         */
+        @Override
+        public final boolean test(final SingleCRS crs1, final SingleCRS crs2) {
+            return comparator(crs1).test(crs2);
+        }
+
+        /**
+         * Returns a mask of the dimensions where a component matches one of 
the {@code search} elements.
+         * The search is lenient, with "lenient" defined by this enumeration 
value.
+         *
+         * <p>If {@code overwrite} is true, this method overwrites the {@code 
search} array.
+         * The {@code overwrite} flag should be {@code false} for 
user-supplied arrays.</p>
+         *
+         * @param  components  value of {@code getSingleComponents(crs)}.
+         * @param  search      the elements to search in the {@code 
components} list.
+         * @param  overwrite   whether to overwrite the {@code search} array 
for matching elements only once.
+         * @return mask with indexes of dimensions where a {@code search} 
element is leniently found.
+         */
+        final BitSet locateDimensions(final List<SingleCRS> components, final 
SingleCRS[] search, final boolean overwrite) {
+            final var mask = new BitSet();
+            if (search != null) {
+                int count = search.length;
+                for (final EquivalenceProperty criterion : values()) {
+                    /*
+                     * Try more specific criteria before this criterion. For 
example, if `this` is `DATUM_TYPE`,
+                     * matches will first be attempted using the `COMPONENT` 
and `DATUM` criteria in that order.
+                     * More restrictive criteria are tried first because datum 
comparisons may be ambiguous.
+                     * For example, some meteorological data have two temporal 
axes, which can result in two
+                     * equivalent temporal datums.
+                     */
+                    int lower = 0;
+                    for (final SingleCRS component : components) {
+                        final Predicate<SingleCRS> comparator = 
criterion.comparator(component);
+                        final int upper = lower + 
getDimensionOrZero(component);
+                        for (int i=0; i<count; i++) {
+                            if (comparator.test(search[i])) {
+                                mask.set(lower, upper);
+                                if (overwrite) {
+                                    // Remove this component from the elements 
to test.
+                                    if (--count == 0) return mask;  // 
Short-circuit.
+                                    System.arraycopy(search, i+1, search, i, 
count - i);
+                                }
+                                break;
+                            }
+                        }
+                        lower = upper;
+                    }
+                    if (criterion == this) {
+                        // Finished execution with the user requested 
criterion.
+                        break;
+                    }
+                }
+            }
+            return mask;
+        }
+    }
+
+    /**
+     * Returns a mask of the {@code crs} dimensions where a component is 
equivalent to a {@code search} elements.
+     * The returned {@code BitSet} contains bits that are set to {@code true} 
at indexes equal to all {@code crs}
+     * dimensions where a {@linkplain 
#getSingleComponents(CoordinateReferenceSystem) single component} of
+     * {@code crs} is {@linkplain #equivalent equivalent} to an element of the 
given {@code search} array.
+     * In other words, if <var>i</var> is the first dimension of a single 
{@code crs} component and <var>n</var>
+     * is its number of dimensions, and if that component is equivalent to an 
element of the {@code search} array,
+     * then bits in the range from <var>i</var> to <var>i</var>+<var>n</var>−1 
inclusive are set to {@code true}.
+     *
+     * <p>The order of elements in the {@code search} array is not significant,
+     * but the axis order and units of measurement inside each {@link 
SingleCRS} is significant.
+     * For more lenient methods ignoring also axis order, axis units and (to 
some extent) to <abbr>CRS</abbr> type,
+     * see the {@link #locateDimensions(CoordinateReferenceSystem, 
CoordinateReferenceSystem, EquivalenceProperty)}
+     * method.</p>
+     *
+     * <p>This method is null-safe: if {@code crs} or {@code search} is {@code 
null}, this method returns an empty set.
+     * If {@code search} contains {@code null} elements, these elements are 
ignored. The latter tolerance makes easy to
+     * use directly the return value of methods such as {@link 
#getTemporalComponent(CoordinateReferenceSystem)}.</p>
+     *
+     * <p><b>Example:</b>
+     * the following snippet gets the dimensions in {@code crs} of the 
temporal and vertical components,
+     * if these components are present and have the same axis order and units 
of measurement:</p>
      *
      * {@snippet lang="java" :
      *     CoordinateReferenceSystem crs = ...;
@@ -1253,34 +1430,40 @@ public final class CRS {
      * <p><b>Tip 2:</b> for locating all dimensions <em>except</em> the 
vertical and temporal ones, use
      * <code>mask.{@linkplain BitSet#flip(int, int) flip}(0, 
CRS.getDimensionOrZero(crs))</code>.</p>
      *
-     * <h4>Null values</h4>
-     * This method is null-safe: if {@code crs} or {@code subCRS} is {@code 
null}, this method returns an empty set.
-     * If {@code subCRS} contains {@code null} elements, these elements are 
ignored. The latter makes easy to use
-     * directly the return value of methods such as {@link 
#getTemporalComponent(CoordinateReferenceSystem)}.
-     *
      * @param  crs     the <abbr>CRS</abbr> for which the indexes of some 
dimensions are wanted.
-     * @param  subCRS  the <abbr>CRS</abbr>s to compare with {@code crs} or 
{@code crs} components.
-     * @return indexes of the dimensions where a {@code subCRS} element is 
found in {@code crs}.
+     * @param  search  the components to compare with {@code crs} or 
components of {@code crs}.
+     * @return mask indicating, for each dimension of {@code crs}, whether 
that dimension is part
+     *         of a component which is equivalent to an element of the {@code 
search} array.
      *
      * @see #selectDimensions(CoordinateReferenceSystem, BitSet, 
SeparationMode)
      *
      * @since 1.6
      */
-    public static BitSet locateDimensions(final CoordinateReferenceSystem crs, 
final SingleCRS... subCRS) {
-        final var mask = new BitSet();
-        if (subCRS != null) {
-            int lower = 0;
-            for (final CoordinateReferenceSystem component : 
getSingleComponents(crs)) {
-                final int upper = lower + getDimensionOrZero(component);
-                for (final CoordinateReferenceSystem search : subCRS) {
-                    if (equivalent(component, search)) {
-                        mask.set(lower, upper);
-                    }
-                }
-                lower = upper;
-            }
-        }
-        return mask;
+    public static BitSet locateDimensions(final CoordinateReferenceSystem crs, 
final SingleCRS... search) {
+        return 
EquivalenceProperty.COMPONENT.locateDimensions(getSingleComponents(crs), 
search, false);
+    }
+
+    /**
+     * Returns a mask of the {@code crs} dimensions where an equivalence with 
a {@code search} component is found.
+     * This method is a more lenient version of {@link 
#locateDimensions(CoordinateReferenceSystem, SingleCRS...)}
+     * where coordinate systems (their types, axis orders, directions and 
units of measurement) can be ignored.
+     * In addition, some differences in <abbr>CRS</abbr> types and datums can 
optionally be tolerated.
+     *
+     * @param  crs        the <abbr>CRS</abbr> for which the indexes of some 
dimensions are wanted.
+     * @param  search     the <abbr>CRS</abbr>s to compare with {@code crs} or 
components of {@code crs}.
+     * @param  criterion  the {@link SingleCRS} property to compare for 
deciding if two instances are equivalent.
+     * @return mask indicating, for each dimension of {@code crs}, whether 
that dimension is part
+     *         of a component which is equivalent to a component of the {@code 
search} <abbr>CRS</abbr>.
+     *
+     * @see #locateDimensions(CoordinateReferenceSystem, SingleCRS...)
+     *
+     * @since 1.8
+     */
+    public static BitSet locateDimensions(final CoordinateReferenceSystem crs,
+                                          final CoordinateReferenceSystem 
search,
+                                          final EquivalenceProperty criterion)
+    {
+        return criterion.locateDimensions(getSingleComponents(crs), 
getSingleComponents(search).toArray(SingleCRS[]::new), true);
     }
 
     /**
@@ -1290,14 +1473,14 @@ public final class CRS {
      * the given mask is updated to the dimensions that the {@code crs} 
dimensions that were effectively retained.
      * The return value is always a <abbr>CRS</abbr> with axes in the same 
order as the given {@code crs}.
      *
-     * <h4>Ellipsoidal height</h4>
+     * <h4>Extraction of ellipsoidal height</h4>
      * This method can transform a three-dimensional geographic 
<abbr>CRS</abbr> into a two-dimensional geographic
      * <abbr>CRS</abbr>, i.e. this method can do the converse of {@link 
#compound(CoordinateReferenceSystem...)}.
      * This method can also extract the {@linkplain 
CommonCRS.Vertical#ELLIPSOIDAL ellipsoidal height}
      * from a three-dimensional geographic <abbr>CRS</abbr>, but this is 
generally not recommended because
      * ellipsoidal heights make little sense without the (<var>latitude</var>, 
<var>longitude</var>) coordinates.
      *
-     * <h4>Unseparable <abbr>CRS</abbr></h4>
+     * <h4>Requesting parts of nondivisible <abbr>CRS</abbr></h4>
      * It is illegal to extract only the latitude or longitude axis from a 
geodetic <abbr>CRS</abbr>.
      * Similar constraints exist also for projected and engineering 
<abbr>CRS</abbr>. If {@code crs}
      * or a component of {@code crs} cannot be separated as requested by the 
{@code dimensions} mask,
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
index 3960e2a947..901c744138 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.referencing.operation;
 
+import java.util.BitSet;
 import java.util.List;
 import org.opengis.referencing.crs.*;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
@@ -271,34 +272,37 @@ searchSrc:  while (sourceComponentIndex < 
sourceComponentIsUsed.length) {
      * <abbr>CRS</abbr>, or {@code null} if that axis is unknown.
      *
      * @param  context  options supplied by the user, or {@code null}.
-     * @return the coordinate axis that could not be resolved, or {@code null} 
if none or unknown.
+     * @return the first coordinate axis that could not be resolved, or {@code 
null} if none or unknown.
      */
     private CoordinateSystemAxis 
fetchConstantsForMissingSourceDimensions(final CoordinateOperationContext 
context) {
         if (context != null) {
             final DirectPosition coordinates = 
context.getConstantCoordinates();
             if (coordinates != null) {
                 /*
-                 * Finds the index of the first coordinate to use among the 
constant coordinates.
+                 * Finds the indexes of the coordinates to use among the 
constant coordinates.
                  * The default CRS of `coordinates` is the full target CRS 
(with all dimensions).
-                 * If a different CRS is specified, search the index of this 
target CRS component.
+                 * If a different CRS is specified, search the indexes of that 
target component.
                  * No coordinate transformation is perfomed in this method, 
only selection.
                  */
-                int indexOfConstant = targetLowerDimension;     // Value for 
the default CRS.
+                final BitSet indexOfConstant;
                 final CoordinateReferenceSystem crs = 
coordinates.getCoordinateReferenceSystem();
                 if (crs != null) {
-                    indexOfConstant = CRS.locateDimensions(crs, 
targetComponent).nextSetBit(0);
-                    if (indexOfConstant < 0) {
-                        return null;
-                    }
+                    indexOfConstant = CRS.locateDimensions(crs, 
targetComponent);
+                } else {
+                    indexOfConstant = new BitSet();
+                    indexOfConstant.set(targetLowerDimension, 
targetUpperDimension);
                 }
-                final int d = coordinates.getDimension();
-                final var c = new double[targetUpperDimension - 
targetLowerDimension];
-                for (int i=0; i<c.length; i++) {
-                    if (indexOfConstant >= d || Double.isNaN(c[i] = 
coordinates.getCoordinate(indexOfConstant++))) {
-                        return 
targetComponent.getCoordinateSystem().getAxis(i);
+                if (!indexOfConstant.isEmpty()) {
+                    final var c = new double[targetUpperDimension - 
targetLowerDimension];
+                    for (int next = -1, i=0; i<c.length; i++) {
+                        next = indexOfConstant.nextSetBit(next + 1);
+                        if (next < 0 || Double.isNaN(c[i] = 
coordinates.getCoordinate(next))) {
+                            // Coordinate cannot be resolved. Return an object 
to use in error message.
+                            return 
targetComponent.getCoordinateSystem().getAxis(i);
+                        }
                     }
+                    constantCoordinates = c;
                 }
-                constantCoordinates = c;
             }
         }
         return null;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/package-info.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/package-info.java
index e64ead2434..bdd398078e 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/package-info.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/package-info.java
@@ -42,7 +42,7 @@
  * {@link org.apache.sis.referencing.CRS#fromWKT(String)} and
  * {@link org.apache.sis.referencing.CRS#fromXML(String)}
  *
- * <h2>Usage example</h2>
+ * <h3>Usage example</h3>
  * The following example projects a (<var>latitude</var>, 
<var>longitude</var>) coordinate to
  * a <cite>Universal Transverse Mercator</cite> projection in the zone of the 
coordinate:
  *
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
index ac6cda0794..2158ca67aa 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
@@ -416,13 +416,77 @@ public final class CRSTest extends TestCaseWithLogs {
     }
 
     /**
-     * Tests getting a mask of some CRS components.
+     * Tests getting a mask of some <abbr>CRS</abbr> components.
      */
     @Test
     public void testLocateDimensions() {
-        // Following currently locate only the temporal CRS because geographic 
CRS is not separated in 2D+1D parts.
-        BitSet mask = CRS.locateDimensions(HardCodedCRS.WGS84_4D, 
HardCodedCRS.TIME, HardCodedCRS.ELLIPSOIDAL_HEIGHT);
+        // Following currently locates only the temporal CRS because 
geographic CRS is not separated in 2D+1D parts.
+        SingleCRS[] args = {HardCodedCRS.TIME, 
HardCodedCRS.ELLIPSOIDAL_HEIGHT};
+        SingleCRS[] save = args.clone();
+        BitSet mask = CRS.locateDimensions(HardCodedCRS.WGS84_4D, args);
         assertEquals(BitSet.valueOf(new long[] {0b1000}), mask);
+        assertArrayEquals(save, args);     // Verify that the arguments were 
not overwritten.
+
+        args = new SingleCRS[] {HardCodedCRS.TIME, 
HardCodedCRS.GRAVITY_RELATED_HEIGHT};
+        save = args.clone();
+        mask = CRS.locateDimensions(HardCodedCRS.GEOID_4D, args);
+        assertEquals(BitSet.valueOf(new long[] {0b1100}), mask);
+        assertArrayEquals(save, args);
+
+        // Should not take the vertical dimension because CRSs are not 
equivalent.
+        args = new SingleCRS[] {HardCodedCRS.TIME, HardCodedCRS.DEPTH};
+        save = args.clone();
+        mask = CRS.locateDimensions(HardCodedCRS.GEOID_4D, args);
+        assertEquals(BitSet.valueOf(new long[] {0b1000}), mask);
+        assertArrayEquals(save, args);
+    }
+
+    /**
+     * Tests getting a mask of some <abbr>CRS</abbr> components by datum 
comparisons.
+     *
+     * @throws FactoryException if an error occurred while creating a test 
compound <abbr>CRS</abbr>.
+     */
+    @Test
+    public void testEquivalenceProperty() throws FactoryException {
+        for (final CRS.EquivalenceProperty criterion : 
CRS.EquivalenceProperty.values()) {
+            final String name = criterion.name();
+            BitSet mask;
+
+            mask = CRS.locateDimensions(
+                    HardCodedCRS.GEOID_4D,
+                    HardCodedCRS.GEOID_3D,
+                    criterion);
+            assertEquals(BitSet.valueOf(new long[] {0b0111}), mask, name);
+
+            mask = CRS.locateDimensions(
+                    HardCodedCRS.GEOID_4D,
+                    CRS.compound(HardCodedCRS.TIME, 
HardCodedCRS.GRAVITY_RELATED_HEIGHT),
+                    criterion);
+            assertEquals(BitSet.valueOf(new long[] {0b1100}), mask, name);
+
+            mask = CRS.locateDimensions(
+                    HardCodedCRS.GEOID_4D,
+                    CRS.compound(HardCodedCRS.TIME, HardCodedCRS.DEPTH),
+                    criterion);
+            assertEquals(BitSet.valueOf(new long[] {
+                switch(criterion) {
+                    case COMPONENT         -> 0b1000;   // No vertical 
dimension because CRS is not equivalent.
+                    case DATUM, DATUM_TYPE -> 0b1100;   // Accept vertical 
dimension because datum is the same.
+                }
+            }), mask, name);
+
+            mask = CRS.locateDimensions(
+                    HardCodedCRS.GEOID_4D,
+                    CRS.compound(HardCodedCRS.NTF, HardCodedCRS.DEPTH),
+                    criterion);
+            assertEquals(BitSet.valueOf(new long[] {
+                switch(criterion) {
+                    case COMPONENT  -> 0b0000;   // No equivalent CRS.
+                    case DATUM      -> 0b0100;   // No horizontal dimension 
because datum is not the same.
+                    case DATUM_TYPE -> 0b0111;   // Accept horizontal 
dimensions because datum type is the same.
+                }
+            }), mask, name);
+        }
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
index c4c6577c81..f0984e0605 100644
--- 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
+++ 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.storage.netcdf.base;
 
+import java.util.BitSet;
 import java.util.Map;
 import java.util.List;
 import java.util.ArrayList;
@@ -40,7 +41,6 @@ import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterNotFoundException;
 import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.crs.CRSFactory;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.GeographicCRS;
@@ -57,8 +57,6 @@ import org.opengis.referencing.datum.Ellipsoid;
 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.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
@@ -67,7 +65,6 @@ import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.referencing.operation.provider.PseudoPlateCarree;
-import org.apache.sis.referencing.internal.shared.AxisDirections;
 import org.apache.sis.referencing.internal.shared.AffineTransform2D;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.netcdf.internal.Resources;
@@ -1011,33 +1008,27 @@ final class GridMapping {
                 /*
                  * The CRS built by the `Grid` class (based on an inspection 
of coordinate variables)
                  * may have a different axis order than the CRS specified by 
grid mapping attributes
-                 * (the CRS built by this class). This block checks which axis 
order seems to fit,
-                 * then potentially replaces `Grid` implicit CRS by 
`GridMapping` explicit CRS.
-                 *
-                 * This is where the potential difference between EPSG axis 
order and grid axis order is handled.
-                 * If we cannot find which component to replace, assume that 
grid mapping describes the first dimensions.
-                 * We have no guarantees that this latter assumption is 
correct, but it seems to match common practice.
+                 * (the CRS built by this class). This block locates the 
dimensions of the horizontal
+                 * CRS and replaces `Grid` implicit CRS by `GridMapping` 
explicit CRS.
                  */
-                CoordinateReferenceSystem match = explicitCRS;
-                final CoordinateSystem cs = implicitCRS.getCoordinateSystem();
-                firstAffectedCoordinate = AxisDirections.indexOfColinear(cs, 
explicitCRS.getCoordinateSystem());
-                if (firstAffectedCoordinate < 0) {
-                    match = 
AbstractCRS.castOrCopy(explicitCRS).forConvention(AxesConvention.RIGHT_HANDED);
-                    firstAffectedCoordinate = 
AxisDirections.indexOfColinear(cs, match.getCoordinateSystem());
-                    if (firstAffectedCoordinate < 0) {
-                        firstAffectedCoordinate = 0;
-                    }
+                MathTransform swapAxisOrder = null;
+                final BitSet dimensions = CRS.locateDimensions(implicitCRS, 
explicitCRS, CRS.EquivalenceProperty.DATUM_TYPE);
+                firstAffectedCoordinate = Math.max(dimensions.nextSetBit(0), 
0);
+                if (dimensions.nextSetBit(firstAffectedCoordinate + 
CRS.getDimensionOrZero(explicitCRS)) >= 0) {
+                    // Did not found the expected number of dimensions, or 
dimension indexes are not consecutive.
+                    return null;
                 }
-                /*
-                 * Replace the implicit CRS (derived from the netCDF 
dimensions of the variable) by the explicit CRS
-                 * specified by the grid mapping attributes (analyzed by this 
class). The axis order and units may be
-                 * changed from `implicitCRS` order to `explicitCRS` order.
-                 */
-                MathTransform swap = null;
                 try {
+                    /*
+                     * Replace the implicit CRS (derived from the netCDF 
dimensions of the variable) by the explicit CRS
+                     * specified by the grid mapping attributes (analyzed by 
this class). The axis order and units may be
+                     * changed from `implicitCRS` order to `explicitCRS` order.
+                     */
+                    final CoordinateReferenceSystem match = 
CRS.selectDimensions(
+                            implicitCRS, dimensions, CRS.SeparationMode.EXACT);
                     // Following is useful only if a "grid to CRS" exists.
                     if (explicitG2C != null && match != explicitCRS) {
-                        swap = MathTransforms.linear(
+                        swapAxisOrder = MathTransforms.linear(
                                 CoordinateSystems.swapAndScaleAxes(
                                         match.getCoordinateSystem(),
                                         explicitCRS.getCoordinateSystem()));
@@ -1048,10 +1039,10 @@ final class GridMapping {
                     cannotCreateGridOrCRS(variable, e, false);
                     return null;
                 }
-                if (swap != null) {  // Implies `explicitG2C != null`
-                    swap = MathTransforms.passThrough(firstAffectedCoordinate, 
swap,
-                            explicitG2C.getTargetDimensions() - 
(firstAffectedCoordinate + swap.getSourceDimensions()));
-                    explicitG2C = MathTransforms.concatenate(explicitG2C, 
swap);
+                if (swapAxisOrder != null) {  // Implies `explicitG2C != null`
+                    swapAxisOrder = 
MathTransforms.passThrough(firstAffectedCoordinate, swapAxisOrder,
+                            explicitG2C.getTargetDimensions() - 
(firstAffectedCoordinate + swapAxisOrder.getSourceDimensions()));
+                    explicitG2C = MathTransforms.concatenate(explicitG2C, 
swapAxisOrder);
                 }
                 if (implicitCRS.equals(explicitCRS)) {
                     explicitCRS = implicitCRS;          // Keep existing 
instance.
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
index ac99f02986..97885ca378 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
@@ -137,8 +137,8 @@ public final class Classes {
      * This information cannot be obtained from a {@link Class} instance
      * because of the way parameterized types are implemented in Java (by 
erasure).
      *
-     * <h4>Examples</h4>
-     * When invoking this method for a field of the following types:
+     * <p><b>Example:</b>
+     * when invoking this method for a field of the following types:</p>
      * <ul>
      *   <li>{@code Map<String,Number>}: returns {@code String.class}, the 
type of keys.</li>
      *   <li>{@code Set<Number>}: returns {@code Number.class}.</li>
@@ -200,8 +200,8 @@ public final class Classes {
      * the same heuristic rule as {@link #boundOfParameterizedProperty(Field)}
      * (see the javadoc of that method for details).
      *
-     * <h4>Examples</h4>
-     * When invoking this method with the following {@link Class} argument 
values:
+     * <p><b>Example:</b>
+     * when invoking this method with the following {@link Class} argument 
values:</p>
      * <ul>
      *   <li>{@code List.class}: returns {@code Object.class} because {@link 
java.util.List} is declared as
      *       {@code List<E>} (implicitly {@code <E extends Object>}).</li>
@@ -438,9 +438,9 @@ public final class Classes {
      * then the returned set will contain {@link java.util.List} (which is 
implemented directly)
      * together with its parent interfaces {@link Collection} and {@link 
Iterable}.
      *
-     * <h4>Elements ordering</h4>
-     * All interfaces implemented directly by the given type are first and in 
the order they are declared
-     * in the {@code implements} or {@code extends} clause. Interfaces 
implemented by parent interfaces are next.
+     * <p><b>Elements ordering:</b>
+     * All interfaces implemented directly by the given type are first and in 
the order they are declared in the
+     * {@code implements} or {@code extends} clause. Interfaces implemented by 
parent interfaces are next.</p>
      *
      * @param  <T>   the compile-time type of the {@code Class} argument.
      * @param  type  the class or interface for which to get all implemented 
interfaces, or {@code null}.
@@ -488,13 +488,13 @@ public final class Classes {
      * This method invokes itself recursively for adding parent interfaces.
      * The given type is <em>not</em> added to the set.
      *
-     * <h4>Elements ordering</h4>
+     * <p><b>Elements ordering:</b>
      * All interfaces directly implemented by the given type are added first. 
Then parent interfaces
      * are added recursively. The goal is to increase the chances to have the 
most specific types first.
      * Example: suppose a class implementing two interfaces: {@code A extends 
C} and {@code B extends C}.
      * If the parents of A were added immediately after A, we would get {A, C, 
B} order.
      * But if instead we add all directly implemented interfaces before to add 
parents,
-     * then we get {A, B, C} order, which is better.
+     * then we get {A, B, C} order, which is better.</p>
      *
      * @param  type      the type for which to add the interfaces in the given 
set.
      * @param  baseType  the base type of the interfaces to examine.
@@ -632,10 +632,10 @@ next:       for (final Class<?> candidate : candidates) {
      * Only interfaces assignable to {@code baseInterface} are compared.
      * Declaration order does not matter.
      *
-     * <h4>Example</h4>
+     * <p><b>Example:</b>
      * in ISO 19111, different interfaces exist for different coordinate 
system (CS) geometries
      * ({@code CartesianCS}, {@code PolarCS}, etc.). One can check if two 
implementations have
-     * the same geometry with the following code:
+     * the same geometry with the following code:</p>
      *
      * {@snippet lang="java" :
      *     if (implementSameInterfaces(cs1, cs2, CoordinateSystem.class)) {
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java
index 30557b91a4..55bf24f7da 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java
@@ -133,7 +133,7 @@ public enum ComparisonMode {
      * However, the changes should not have a practical impact on the 
numerical results of coordinate operations.
      * For example, changes of input or output axis order is not accepted by 
this comparison mode.
      *
-     * <h4>Application to datum ensembles</h4>
+     * <h4>Compatible comparison between datum and datum ensemble</h4>
      * Two Coordinate Reference Systems (<abbr>CRS</abbr>) may be considered 
compatible when
      * one <abbr>CRS</abbr> is associated with a datum and the other 
<abbr>CRS</abbr> is associated with a datum ensemble,
      * but the former can be considered as the {@linkplain 
org.apache.sis.referencing.datum.DatumOrEnsemble#isLegacyDatum
@@ -145,7 +145,7 @@ public enum ComparisonMode {
      * but as defined in version 9 and 10 respectively of the 
<abbr>EPSG</abbr> database.
      * They are the <abbr>CRS</abbr> definitions before and after the 
introduction of datum ensemble in the schema.</p>
      *
-     * <h4>Application to dynamic reference frames</h4>
+     * <h4>Compatible comparison between static and dynamic reference 
frames</h4>
      * Two Reference Frames may be considered compatible despite one frame 
being static and the other frame being dynamic.
      * This comparison mode can check, among other criteria, whether the two 
reference frames share a common authority code
      * or have an {@linkplain 
org.apache.sis.referencing.IdentifiedObjects#isHeuristicMatchForName equivalent 
name}.
@@ -153,6 +153,7 @@ public enum ComparisonMode {
      * <p><b>Example:</b> the "WGS 1972" reference frame as defined in 
versions 9 and 10 of <abbr>EPSG</abbr> database.</p>
      *
      * @see #isCompatibility()
+     * @see 
org.apache.sis.referencing.CRS#equivalent(CoordinateReferenceSystem, 
CoordinateReferenceSystem)
      *
      * @since 1.5
      */
@@ -185,7 +186,8 @@ public enum ComparisonMode {
      * but are considered equivalent according this {@code ALLOW_VARIANT} mode.
      *
      * <ul class="verbose">
-     *   <li>Two Coordinate Reference Systems (<abbr>CRS</abbr>) with the same 
axes but in different order.
+     *   <li>Two Coordinate Reference Systems (<abbr>CRS</abbr>) with the same 
axes but in different order,
+     *       or using different units of measurement, or orientated in 
different directions.
      *       <b>Example:</b> two geographic <abbr>CRS</abbr>s with the same 
attributes but with
      *       (<var>latitude</var>, <var>longitude</var>) axes in one case and
      *       (<var>longitude</var>, <var>latitude</var>) axes in the other 
case.</li>
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Utilities.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Utilities.java
index b236c50b22..36a384d9c0 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Utilities.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Utilities.java
@@ -24,6 +24,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.Objects;
 import java.util.Optional;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.util.collection.CheckedContainer;
 
 
@@ -73,6 +74,7 @@ public final class Utilities {
      *
      * @see #deepEquals(Object, Object, ComparisonMode)
      * @see ComparisonMode#IGNORE_METADATA
+     * @see 
org.apache.sis.referencing.CRS#equivalent(CoordinateReferenceSystem, 
CoordinateReferenceSystem)
      */
     public static boolean equalsIgnoreMetadata(final Object object1, final 
Object object2) {
         return deepEquals(object1, object2, ComparisonMode.IGNORE_METADATA);

Reply via email to