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

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

commit 4e0bc740bc0d77e5e2806420a0c5e2f8789cf59e
Merge: 236ded5a92 c9641c4260
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sun Aug 31 17:23:57 2025 +0200

    Merge branch 'geoapi-4.0' into geoapi-3.1.
    The merge improves the way that the authority factory search for EPSG codes 
in the database.
    This review was necessary because of new datum types, but also improves 
performance.
    This merge also fixes more test cases with version 12 of the EPSG database.

 .../org/apache/sis/console/CRSCommandTest.java     |   2 +-
 .../sis/coverage/grid/DimensionalityReduction.java |   2 +-
 .../org/apache/sis/coverage/grid/GridExtent.java   |   2 +-
 .../org/apache/sis/coverage/grid/GridGeometry.java |   2 +-
 .../sis/coverage/grid/ResampledGridCoverage.java   |   7 +-
 .../geometry/wrapper/SpatialOperationContext.java  |   9 +-
 .../org/apache/sis/geometry/wrapper/jts/JTS.java   |   5 +-
 .../coverage/grid/DimensionalityReductionTest.java |   2 +-
 .../metadata/iso/extent/DefaultVerticalExtent.java |   3 +-
 .../sis/metadata/privy/NameToIdentifier.java       |   2 +-
 .../sis/metadata/sql/privy/SQLUtilities.java       |  61 +-
 .../org/apache/sis/metadata/sql/privy/Syntax.java  |   2 +-
 .../main/org/apache/sis/temporal/TemporalDate.java |   2 +-
 .../sis/metadata/sql/privy/SQLUtilitiesTest.java   |   4 +-
 .../sis/openoffice/ReferencingFunctions.java       |   2 +-
 .../sis/openoffice/ReferencingFunctionsTest.java   |   5 +-
 .../org/apache/sis/map/coverage/RenderingData.java |   3 +-
 .../main/org/apache/sis/portrayal/Canvas.java      |  13 +-
 .../gazetteer/AbstractLocationType.java            |   4 +-
 .../gazetteer/GeohashReferenceSystem.java          |   5 +-
 .../gazetteer/MilitaryGridReferenceSystem.java     |   5 +-
 .../gazetteer/ReferencingByIdentifiers.java        |   4 +-
 .../sis/coordinate/DefaultCoordinateMetadata.java  |   4 +-
 .../sis/geometry/AbstractDirectPosition.java       |   6 +-
 .../org/apache/sis/geometry/AbstractEnvelope.java  |   2 +-
 .../org/apache/sis/geometry/EnvelopeReducer.java   |   5 +-
 .../main/org/apache/sis/geometry/Envelopes.java    |   6 +-
 .../apache/sis/geometry/WraparoundAdjustment.java  |   3 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      |   2 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    | 125 ++--
 .../sis/parameter/DefaultParameterDescriptor.java  |   6 +-
 .../sis/parameter/DefaultParameterValueGroup.java  |   8 +-
 .../parameter/UnmodifiableParameterValueGroup.java |   8 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |  15 +-
 .../apache/sis/referencing/AuthorityFactories.java |  54 +-
 .../main/org/apache/sis/referencing/Builder.java   |   4 +-
 .../main/org/apache/sis/referencing/CRS.java       |  39 +-
 .../main/org/apache/sis/referencing/CommonCRS.java |  40 +-
 .../sis/referencing/DefaultObjectDomain.java       |   3 +-
 .../sis/referencing/EPSGFactoryFallback.java       |  41 +-
 .../apache/sis/referencing/IdentifiedObjects.java  |  39 +-
 .../sis/referencing/StandardDefinitions.java       |  70 ++-
 .../apache/sis/referencing/crs/AbstractCRS.java    |  97 +--
 .../sis/referencing/crs/AbstractDerivedCRS.java    |   4 +-
 .../sis/referencing/crs/AbstractSingleCRS.java     |  53 +-
 .../sis/referencing/crs/DefaultCompoundCRS.java    |  50 +-
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  46 +-
 .../sis/referencing/crs/DefaultEngineeringCRS.java |  10 +
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  16 +-
 .../sis/referencing/crs/DefaultParametricCRS.java  |  10 +
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  18 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |   9 +
 .../sis/referencing/crs/DefaultVerticalCRS.java    |  10 +
 .../org/apache/sis/referencing/cs/AbstractCS.java  |  93 +--
 .../sis/referencing/cs/DefaultCompoundCS.java      |  40 +-
 .../cs/DefaultCoordinateSystemAxis.java            |   4 +-
 .../sis/referencing/datum/AbstractDatum.java       |  10 +-
 .../sis/referencing/datum/DatumOrEnsemble.java     | 272 ++++++--
 .../referencing/datum/DefaultDatumEnsemble.java    |   4 +-
 .../sis/referencing/datum/DefaultEllipsoid.java    |  11 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |   6 +-
 .../sis/referencing/datum/DefaultImageDatum.java   |   4 +-
 .../referencing/datum/DefaultPrimeMeridian.java    |  14 +-
 .../referencing/datum/DefaultTemporalDatum.java    |   4 +-
 .../referencing/datum/DefaultVerticalDatum.java    |  34 +-
 .../factory/AuthorityFactoryIdentifier.java        |  19 +-
 .../factory/ConcurrentAuthorityFactory.java        | 130 +++-
 .../factory/IdentifiedObjectFinder.java            | 686 +++++++++++++--------
 .../factory/MultiAuthoritiesFactory.java           | 113 +++-
 .../referencing/factory/sql/AuthorityCodes.java    | 177 ++++--
 .../factory/sql/CloseableReference.java            |   6 +
 .../referencing/factory/sql/EPSGCodeFinder.java    | 395 ++++++++++--
 .../referencing/factory/sql/EPSGDataAccess.java    | 531 ++++++++--------
 .../sis/referencing/factory/sql/EPSGFactory.java   |  32 +-
 .../sis/referencing/factory/sql/EPSGInstaller.java |  27 +-
 .../factory/sql/InstallationScriptProvider.java    | 198 +-----
 .../referencing/factory/sql/ObjectPertinence.java  |  11 +-
 .../sis/referencing/factory/sql/SQLTranslator.java |  34 +-
 .../sis/referencing/factory/sql/TableInfo.java     | 501 +++++++++------
 .../referencing/internal/EPSGParameterDomain.java  |   4 +-
 .../sis/referencing/internal/MergedProperties.java |   4 +-
 .../referencing/internal/PositionTransformer.java  |   6 +-
 .../apache/sis/referencing/internal/RTreeNode.java |   4 +-
 .../referencing/internal/VerticalDatumTypes.java   |  16 +-
 .../operation/AbstractCoordinateOperation.java     |  20 +-
 .../operation/AbstractSingleOperation.java         |   4 +-
 .../apache/sis/referencing/operation/CRSPair.java  |   7 +-
 .../operation/CoordinateOperationFinder.java       |   2 +-
 .../operation/CoordinateOperationRegistry.java     |   9 +-
 .../operation/DefaultConcatenatedOperation.java    |   1 +
 .../operation/DefaultOperationMethod.java          |  10 +-
 .../sis/referencing/operation/matrix/Matrices.java |  15 +-
 .../operation/projection/NormalizedProjection.java |   6 +-
 .../operation/provider/Mercator1SP.java            |   7 +-
 .../operation/provider/ObliqueMercator.java        |  12 +-
 .../operation/provider/PolarStereographicA.java    |   2 +-
 .../operation/provider/PseudoMercator.java         |   7 +-
 .../operation/provider/TransverseMercator.java     |   2 +-
 .../operation/transform/AbstractMathTransform.java |   2 +-
 .../operation/transform/ConcatenatedTransform.java |  15 +-
 .../transform/EllipsoidToRadiusTransform.java      |   6 +-
 .../operation/transform/PoleRotation.java          |   1 +
 .../sis/referencing/privy/AxisDirections.java      |   4 +-
 .../sis/referencing/privy/FilteredIterator.java    | 113 ++++
 .../org/apache/sis/referencing/privy/LazySet.java  |  25 +-
 .../apache/sis/referencing/privy/WKTUtilities.java |  32 -
 .../org/apache/sis/referencing/Assertions.java     |  69 ++-
 .../sis/referencing/AuthorityFactoriesTest.java    |   2 +-
 .../test/org/apache/sis/referencing/CRSTest.java   |   4 +-
 .../org/apache/sis/referencing/CommonCRSTest.java  |   4 +-
 .../sis/referencing/EPSGFactoryFallbackTest.java   |   2 +-
 .../sis/referencing/StandardDefinitionsTest.java   |  29 +
 .../factory/CommonAuthorityFactoryTest.java        |   4 +-
 .../factory/ConcurrentAuthorityFactoryTest.java    |   2 +-
 .../factory/GeodeticObjectFactoryTest.java         |   3 +-
 .../factory/IdentifiedObjectFinderTest.java        |   3 +-
 .../referencing/factory/sql/EPSGFactoryTest.java   |  59 +-
 .../referencing/factory/sql/EPSGInstallerTest.java |  20 +-
 .../factory/sql/EPSGScriptProvider.java            | 113 ++++
 .../sis/referencing/factory/sql/TableInfoTest.java |  14 +-
 .../transform/DefaultMathTransformFactoryTest.java |   2 +-
 .../report/CoordinateOperationMethods.java         |   2 +-
 .../sis/test/integration/ConsistencyTest.java      |  63 +-
 .../integration/CoordinateReferenceSystemTest.java |  33 -
 .../sis/storage/geotiff/reader/CRSBuilder.java     |   6 +-
 .../apache/sis/storage/netcdf/base/AxisType.java   |   2 +-
 .../apache/sis/storage/netcdf/base/CRSMerger.java  |   4 +-
 .../sis/storage/sql/feature/InfoStatements.java    |  14 +-
 .../sis/storage/sql/feature/SelectionClause.java   |   4 +-
 .../sis/storage/aggregate/CoverageAggregator.java  |   2 +-
 .../apache/sis/storage/aggregate/GroupByCRS.java   |   4 +-
 .../apache/sis/storage/image/WritableStore.java    |   2 +-
 .../apache/sis/setup/InstallationResources.java    |   2 +-
 .../main/org/apache/sis/util/CharSequences.java    |  66 +-
 .../main/org/apache/sis/util/ComparisonMode.java   | 146 +++--
 .../org/apache/sis/util/LenientComparable.java     |  18 +-
 .../main/org/apache/sis/util/Utilities.java        |   2 +-
 .../org/apache/sis/util/collection/TreeTable.java  |   2 +-
 .../main/org/apache/sis/util/logging/Logging.java  |   2 +-
 .../main/org/apache/sis/util/privy/Constants.java  |   1 +
 .../main/org/apache/sis/util/privy/Numerics.java   |   2 +-
 .../apache/sis/util/privy/SetOfUnknownSize.java    |   6 +-
 .../main/org/apache/sis/util/resources/Errors.java |   7 +-
 .../apache/sis/util/resources/Errors.properties    |   1 -
 .../apache/sis/util/resources/Errors_fr.properties |   1 -
 .../test/org/apache/sis/test/Assertions.java       |   4 +-
 .../org/apache/sis/util/CharSequencesTest.java     |   5 +-
 .../main/org/apache/sis/geometries/Geometries.java |   3 +-
 .../org/apache/sis/geometries/PreparedTIN.java     |  12 +-
 .../apache/sis/geometries/math/AbstractTuple.java  |   4 +-
 .../main/org/apache/sis/geometries/math/Tuple.java |   4 +-
 .../geometries/mesh/MeshPrimitiveComparator.java   |   6 +-
 .../sis/geometries/mesh/MultiMeshPrimitive.java    |   4 +-
 .../sis/geometries/processor/ProcessorUtils.java   |   8 +-
 .../simplify/greedyinsert/TINBuilder.java          |   4 +-
 netbeans-project/nbproject/project.xml             |   1 +
 optional/build.gradle.kts                          |  18 +-
 .../main/org/apache/sis/gui/map/StatusBar.java     |   2 +-
 .../gui/referencing/PositionableProjection.java    |   3 +-
 .../sis/resources/embedded/EmbeddedResources.java  |  14 +-
 .../sis/resources/embedded/package-info.java       |  12 +-
 .../resources/embedded/EmbeddedResourcesTest.java  |  40 +-
 .../apache/sis/resources/embedded/Generator.java   | 117 ++--
 .../sis/referencing/factory/sql/epsg/.gitignore    |   5 +-
 .../sis/referencing/factory/sql/epsg/Finish.sql    |   0
 .../sis/referencing/factory/sql/epsg/Prepare.sql   |   4 +-
 .../sis/referencing/factory/sql/epsg/README.md     |  13 +-
 .../factory/sql/epsg/ScriptProvider.java           |  32 +-
 .../referencing/factory/sql/epsg/DebugTools.sql    |  10 +
 .../factory/sql/epsg/ScriptProviderTest.java       |   4 +-
 170 files changed, 3531 insertions(+), 2161 deletions(-)

diff --cc 
endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java
index d4e8343eaa,5644549f9e..fd0f65165a
--- 
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java
+++ 
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java
@@@ -797,8 -796,8 +796,8 @@@ public class Canvas extends Observable 
      public void setDisplayBounds(final Envelope newValue) throws 
RenderException {
          ArgumentChecks.ensureNonNull(DISPLAY_BOUNDS_PROPERTY, newValue);
          final CoordinateReferenceSystem crs = 
newValue.getCoordinateReferenceSystem();
-         if (crs != null && !Utilities.equalsIgnoreMetadata(getDisplayCRS(), 
crs)) {
+         if (crs != null && !CRS.equivalent(getDisplayCRS(), crs)) {
 -            throw new 
MismatchedCoordinateMetadataException(errors().getString(
 +            throw new MismatchedReferenceSystemException(errors().getString(
                      Errors.Keys.IllegalCoordinateSystem_1, 
IdentifiedObjects.getDisplayName(crs, getLocale())));
          }
          final GeneralEnvelope oldValue = new GeneralEnvelope(displayBounds);
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
index 4f4fdb5c20,3c5731c819..9624762637
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
@@@ -127,8 -127,8 +127,8 @@@ public abstract class AbstractDirectPos
              final CoordinateReferenceSystem crs = 
getCoordinateReferenceSystem();
              if (crs != null) {
                  final CoordinateReferenceSystem other = 
position.getCoordinateReferenceSystem();
-                 if (other != null && !Utilities.equalsIgnoreMetadata(crs, 
other)) {
+                 if (other != null && !CRS.equivalent(crs, other)) {
 -                    throw new 
MismatchedCoordinateMetadataException(Errors.format(Errors.Keys.MismatchedCRS));
 +                    throw new 
MismatchedReferenceSystemException(Errors.format(Errors.Keys.MismatchedCRS));
                  }
              }
              for (int i=0; i<dimension; i++) {
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index e473ae0c0f,da10560a59..3ebf31d761
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@@ -1679,10 -1676,10 +1680,10 @@@ class GeodeticObjectParser extends Math
          final Unit<?> unit  = parseUnit(element);
          final CoordinateSystem cs;
          try {
-             cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, 
false, unit, datum);
+             cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, 
false, unit, false, null);
              final Map<String,?> properties = parseMetadataAndClose(element, 
name, datum);
              if (cs instanceof AffineCS) {
 -                return new DefaultImageCRS(properties, datum, (AffineCS) cs);
 +                return factories.getCRSFactory().createImageCRS(properties, 
datum, (AffineCS) cs);
              }
          } catch (FactoryException exception) {
              throw element.parseFailed(exception);
@@@ -2156,15 -2126,37 +2129,37 @@@
          }
          final CoordinateSystem cs;
          try {
-             cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, 
isWKT1, csUnit, geoCRS.getDatum());
-             final Map<String,?> properties = parseMetadataAndClose(element, 
name, conversion);
+             cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, 
isWKT1, csUnit, true, null);
              if (cs instanceof CartesianCS) {
+                 final SingleCRS geoCRS = parseGeodeticCRS(MANDATORY, element, 
cs.getDimension(), WKTKeywords.ellipsoidal);
+                 if (!(geoCRS instanceof GeodeticCRS)) {
+                     throw new UnparsableObjectException(errorLocale, 
Errors.Keys.IllegalCRSType_1,
+                             new Object[] {geoCRS.getClass()}, element.offset);
+                 }
                  /*
-                  * TODO: if the CartesianCS is three-dimensional, we need to 
ensure that the base CRS is also
-                  * three-dimensional. We could do that by parsing the CS 
before to invoke `parseGeodeticCRS`.
+                  * Parse the projection parameters. If a default linear unit 
is specified, it will apply to
+                  * all parameters that do not specify explicitly a 
LengthUnit. If no such crs-wide unit was
+                  * specified, then the default will be degrees.
+                  *
+                  * More specifically §9.3.4 in the specification said about 
the default units:
+                  *
+                  *    - lengths shall be given in the unit for the projected 
CRS axes.
+                  *    - angles shall be given in the unit for the base 
geographic CRS of the projected CRS.
                   */
+                 final Unit<Length> linearUnit;
+                 final Unit<Angle>  angularUnit;
+                 if (isWKT1 && usesCommonUnits) {
+                     linearUnit  = Units.METRE;
+                     angularUnit = Units.DEGREE;
+                 } else {
+                     linearUnit  = csUnit;
+                     angularUnit = 
AxisDirections.getAngularUnit(geoCRS.getCoordinateSystem(), Units.DEGREE);
+                 }
+                 final Conversion conversion = 
parseDerivingConversion(MANDATORY, element,
+                         isWKT1 ? null : WKTKeywords.Conversion, linearUnit, 
angularUnit);
+                 final Map<String,?> properties = 
parseMetadataAndClose(element, name, conversion);
                  final CRSFactory crsFactory = factories.getCRSFactory();
 -                return crsFactory.createProjectedCRS(properties, 
(GeodeticCRS) geoCRS, conversion, (CartesianCS) cs);
 +                return crsFactory.createProjectedCRS(properties, 
(GeographicCRS) geoCRS, conversion, (CartesianCS) cs);
              }
          } catch (FactoryException exception) {
              throw element.parseFailed(exception);
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java
index fdcf1d4b52,9fc223597c..5d6f57441a
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java
@@@ -133,15 -130,14 +130,15 @@@ final class EPSGFactoryFallback extend
          final boolean ellipsoid  = type.isAssignableFrom(Ellipsoid    .class);
          final boolean datum      = type.isAssignableFrom(GeodeticDatum.class);
          final boolean geographic = type.isAssignableFrom(GeographicCRS.class);
 -        final boolean geodetic   = type.isAssignableFrom(GeodeticCRS  .class);
 +        @SuppressWarnings("deprecation")
 +        final boolean geocentric = type.isAssignableFrom(GeocentricCRS.class);
          final boolean projected  = type.isAssignableFrom(ProjectedCRS .class);
-         final Set<String> codes = new LinkedHashSet<>();
+         final var codes = new LinkedHashSet<String>();
          if (pm) codes.add(StandardDefinitions.GREENWICH);
          for (final CommonCRS crs : CommonCRS.values()) {
 -            if (ellipsoid) add(codes, crs.ellipsoid);
 -            if (datum)     add(codes, crs.datum);
 -            if (geodetic)  add(codes, crs.geocentric);
 +            if (ellipsoid)  add(codes, crs.ellipsoid);
 +            if (datum)      add(codes, crs.datum);
 +            if (geocentric) add(codes, crs.geocentric);
              if (geographic) {
                  add(codes, crs.geographic);
                  add(codes, crs.geo3D);
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
index 8ba7eb177d,214fe8f0c7..82a0ae2c47
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
@@@ -241,24 -219,20 +244,44 @@@ public class DefaultVerticalDatum exten
      }
  
      /**
 -     * Returns the realization method if it was explicitly specified, or 
otherwise tries to guess it.
 +     * Returns the type of this vertical datum.
 +     *
 +     * <h4>Historical note:</h4>
 +     * This property was defined in the ISO 19111 specification published in 
2003,
 +     * but removed from the revision published 2007.
 +     * This property provides an information similar to the {@linkplain 
#getAnchorPoint() anchor definition},
 +     * but in a programmatic way more suitable to coordinate transformation 
engines.
 +     *
 +     * @return the type of this vertical datum.
 +     *
 +     * @deprecated As of ISO 19111:2019, the {@code VerticalDatumType} 
argument is replaced by {@code RealizationMethod}.
 +     */
 +    @Override
 +    @Deprecated(since = "2.0")  // Temporary version number until this branch 
is released.
 +    public VerticalDatumType getVerticalDatumType() {
 +        return type;
 +    }
 +
++    /**
++     * Returns the datum type if it was explicitly specified, or otherwise 
tries to guess it.
+      * This method may return {@code null} if it cannot guess the method. 
This is used for compatibility
+      * with legacy formats such as WKT 1 or GML 3.1, before realization 
method became a formal property.
+      */
 -    private RealizationMethod getOrGuessMethod(final FormattableObject 
parent) {
 -        return getRealizationMethod().orElseGet(() -> {
++    private VerticalDatumType getOrGuessMethod(final FormattableObject 
parent) {
++        @SuppressWarnings("LocalVariableHidesMemberVariable")
++        final VerticalDatumType type = getVerticalDatumType();
++        if (type != null && type != VerticalDatumType.OTHER_SURFACE) {
++            return type;
++        }
++        return 
VerticalDatumTypes.fromMethod(getRealizationMethod().orElseGet(() -> {
+             CoordinateSystemAxis axis = null;
+             if (parent instanceof CoordinateReferenceSystem) {
+                 axis = ((CoordinateReferenceSystem) 
parent).getCoordinateSystem().getAxis(0);
+             }
+             return VerticalDatumTypes.fromDatum(getName().getCode(), 
getAlias(), axis);
 -        });
++        }));
+     }
+ 
      /**
       * A vertical reference frame in which some of the defining parameters 
have time dependency.
       * The parameter values are valid at the time given by the
@@@ -466,10 -420,13 +487,13 @@@
       *
       * @see <a href="http://issues.apache.org/jira/browse/SIS-160";>SIS-160: 
Need XSLT between GML 3.1 and 3.2</a>
       */
 +    @SuppressWarnings("deprecation")
      @XmlElement(name = "verticalDatumType")
 -    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
 -    private String getTypeElement() {
 +    private VerticalDatumType getTypeElement() {
-         return Context.isGMLVersion(Context.current(), 
LegacyNamespaces.VERSION_3_2) ? null : getVerticalDatumType();
+         if (Context.isGMLVersion(Context.current(), 
LegacyNamespaces.VERSION_3_2)) {
+             return null;
+         }
 -        return VerticalDatumTypes.toLegacyName(getOrGuessMethod(null));
++        return getOrGuessMethod(null);
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
index 2af9d2d64d,6284d56dae..34d842d425
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
@@@ -16,26 -16,35 +16,37 @@@
   */
  package org.apache.sis.referencing.factory;
  
+ import java.util.Iterator;
  import java.util.Set;
- import java.util.LinkedHashSet;
+ import java.util.HashSet;
  import java.util.Objects;
+ import java.util.function.Function;
  import java.util.logging.Level;
  import java.util.logging.LogRecord;
- import org.opengis.util.GenericName;
  import org.opengis.util.FactoryException;
--import org.opengis.metadata.Identifier;
  import org.opengis.referencing.IdentifiedObject;
  import org.opengis.referencing.AuthorityFactory;
  import org.opengis.referencing.NoSuchAuthorityCodeException;
- import org.apache.sis.referencing.AbstractIdentifiedObject;
+ import org.opengis.referencing.datum.Datum;
  import org.apache.sis.referencing.IdentifiedObjects;
+ import org.apache.sis.referencing.datum.DatumOrEnsemble;
+ import org.apache.sis.referencing.privy.FilteredIterator;
+ import org.apache.sis.referencing.privy.LazySet;
  import org.apache.sis.util.ComparisonMode;
+ import org.apache.sis.util.Disposable;
  import org.apache.sis.util.Utilities;
+ import org.apache.sis.util.OptionalCandidate;
  import org.apache.sis.util.privy.Constants;
- import org.apache.sis.system.Semaphores;
  import org.apache.sis.util.collection.BackingStoreException;
  import org.apache.sis.util.logging.Logging;
+ import org.apache.sis.system.Semaphores;
+ 
+ // Specific to the geoapi-3.1 and geoapi-4.0 branches:
+ import org.opengis.referencing.datum.DatumEnsemble;
+ 
++// Specific to the main and geoapi-3.1 branches:
++import org.opengis.referencing.ReferenceIdentifier;
 +
  
  /**
   * Searches in an authority factory for objects approximately equal to a 
given object.
@@@ -576,9 -570,153 +572,153 @@@ public class IdentifiedObjectFinder 
       * @param  object  the object looked up.
       * @return a set of code candidates.
       * @throws FactoryException if an error occurred while fetching the set 
of code candidates.
+      *
+      * @see IdentifiedObject#getIdentifiers()
+      * @see AuthorityFactory#getAuthorityCodes(Class)
+      */
+     protected Iterable<String> getCodeCandidates(final IdentifiedObject 
object) throws FactoryException {
+         /*
+          * Undocumented contract: if the Iterable implements `Dispose`, its 
method will be invoked
+          * after the first match has been found. This is interpreted as a 
hint that the iteration
+          * can stop earlier than it would normally do.
+          */
+         final Class<? extends IdentifiedObject> type = 
proxy.type.asSubclass(IdentifiedObject.class);
+         if (domain == Domain.EXHAUSTIVE_VALID_DATASET) {
+             return factory.getAuthorityCodes(type);
+         }
+         final boolean easy = (domain == Domain.DECLARATION);
 -        final Set<Identifier> identifiers = object.getIdentifiers();
++        final Set<ReferenceIdentifier> identifiers = object.getIdentifiers();
+         if (identifiers.isEmpty()) {
+             return easy ? Set.of() : factory.getAuthorityCodes(type);
+         }
+         return new Codes(factory, identifiers, easy ? null : type);
+     }
+ 
+     /**
+      * Union of identifier codes followed by code candidates fetched from the 
geodetic dataset.
+      * The codes returned by this iterable are, in this order:
+      *
+      * <ol>
 -     *   <li>{@link IdentifiedObject#getIdentifiers()} (filtered with {@link 
#apply(Identifier)})</li>
++     *   <li>{@link IdentifiedObject#getIdentifiers()} (filtered with {@link 
#apply(ReferenceIdentifier)})</li>
+      *   <li>{@link AuthorityFactory#getAuthorityCodes(Class)} (skipped if 
the class is null)</li>
+      * </ol>
+      *
+      * <h2>Implementation note</h2>
+      * This class should not keep a reference to the enclosing class (it is 
static for that reason),
+      * because some subclasses of {@link IdentifiedObjectFinder} are 
short-lived data access objects
+      * holding resources such as database connections.
       */
-     protected Set<String> getCodeCandidates(final IdentifiedObject object) 
throws FactoryException {
-         return 
factory.getAuthorityCodes(proxy.type.asSubclass(IdentifiedObject.class));
 -    private static final class Codes implements Iterable<String>, 
Function<Identifier, String>, Disposable {
++    private static final class Codes implements Iterable<String>, 
Function<ReferenceIdentifier, String>, Disposable {
+         /** Copy of {@link IdentifiedObjectFinder#factory}. */
+         private final AuthorityFactory factory;
+ 
+         /** The identifiers of the object to search. */
 -        private final Set<Identifier> identifiers;
++        private final Set<ReferenceIdentifier> identifiers;
+ 
+         /** Type of objects to request for code candidates, or {@code null} 
for not requesting code candidates. */
+         private Class<? extends IdentifiedObject> type;
+ 
+         /** Code candidates, created when first needed. This collection may 
be costly to create and/or to use. */
+         private Iterable<String> codes;
+ 
+         /**
+          * Creates a new union of identifier codes and candidate codes.
+          *
+          * @param factory      value of {@link 
IdentifiedObjectFinder#factory}.
+          * @param identifiers  identifiers of the object to search.
+          * @param type         type of objects to request for code 
candidates, or {@code null}.
+          */
 -        Codes(final AuthorityFactory factory, final Set<Identifier> 
identifiers, final Class<? extends IdentifiedObject> type) {
++        Codes(final AuthorityFactory factory, final Set<ReferenceIdentifier> 
identifiers, final Class<? extends IdentifiedObject> type) {
+             this.factory     = factory;
+             this.identifiers = identifiers;
+             this.type        = type;
+         }
+ 
+         /**
+          * Invoked when the caller requested to stop the iteration after the 
current group of elements.
+          * A group of elements is either the codes specified by the 
identifiers, or the codes found in
+          * the database. We will avoid to stop in the middle of a group.
+          *
+          * <p>This is an undocumented feature of {@link 
#createFromCodes(IdentifiedObject)}
+          * for stopping an iteration early when at least one match has been 
found.</p>
+          */
+         @Override
+         public void dispose() {
+             type = null;
+         }
+ 
+         /**
+          * Converts the given identifier to a code returned by {@link 
#getIdentifiers()}.
+          * We accept only codes with a namespace (e.g. "AUTHORITY:CODE") for 
avoiding ambiguity.
+          * We do not try to check by ourselves if the identifier is in the 
namespace of the factory,
+          * because calling {@code factory.getAuthorityCodes()} or {@code 
factory.getCodeSpaces()}
+          * may be costly for some implementations.
+          */
+         @Override
 -        public String apply(final Identifier id) {
++        public String apply(final ReferenceIdentifier id) {
+             final String code = IdentifiedObjects.toString(id);
+             return (code.indexOf(Constants.DEFAULT_SEPARATOR) >= 0) ? code : 
null;
+         }
+ 
+         /**
+          * Returns an iterator over codes of the identifiers of the object to 
search.
+          * The iteration does not include the {@code Identifier} of the name 
because, at least
+          * in Apache <abbr>SIS</abbr> implementations, the factories that 
accept object names
+          * already override {@link #getCodeCandidates(IdentifiedObject)} for 
including names.
+          */
+         final Iterator<String> getIdentifiers() {
+             return new FilteredIterator<>(identifiers.iterator(), this);
+         }
+ 
+         /**
+          * Returns an iterator over the code candidates. This method should 
be invoked only in last resort.
+          *
+          * @throws BackingStoreException if an error occurred while fetching 
the collection of authority codes.
+          */
+         final Iterator<String> getAuthorityCodes() {
+             if (codes == null) {
+                 if (type == null) {
+                     codes = Set.of();
+                 } else try {
+                     codes = factory.getAuthorityCodes(type);
+                 } catch (FactoryException e) {
+                     throw new BackingStoreException(e);
+                 }
+             }
+             return codes.iterator();
+         }
+ 
+         /**
+          * Returns an iterator over the identifiers followed by the code 
candidates.
+          */
+         @Override
+         public Iterator<String> iterator() {
+             return new Iterator<String>() {
+                 /** The iterator of the code to return. */
+                 private Iterator<String> codes = getIdentifiers();
+ 
+                 /** Whether {@link #codes} is for code candidates. */
+                 private boolean isCodeCandidates;
+ 
+                 /** Returns whether there is more codes to return. */
+                 @Override public boolean hasNext() {
+                     if (!isCodeCandidates) {
+                         if (codes.hasNext()) return true;
+                         codes = getAuthorityCodes();
+                         isCodeCandidates = true;
+                     }
+                     return codes.hasNext();
+                 }
+ 
+                 /** Returns the next code. */
+                 @Override public String next() {
+                     if (!(isCodeCandidates || codes.hasNext())) {
+                         codes = getAuthorityCodes();
+                     }
+                     return codes.next();
+                 }
+             };
+         }
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
index 872885db81,1c049568bb..e54cb12614
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
@@@ -1846,10 -1782,45 +1848,45 @@@ public class MultiAuthoritiesFactory ex
          }
  
          /**
-          * Delegates to every factories registered in the enclosing {@link 
MultiAuthoritiesFactory},
-          * in iteration order. This method is invoked only if the parent 
class failed to find the
-          * object by its identifiers and by its name. At this point, as a 
last resort, we will scan
-          * over the objects in the database.
+          * Creates objects equal (optionally ignoring metadata) to the 
specified object using the given identifiers.
+          * This method may be used in order to get a fully identified object 
from a partially identified one.
+          *
+          * @param  type         the type of the factory which is needed.
+          * @param  object       the user-specified object to search.
+          * @param  identifiers  {@code object.getIdentifiers()} or {@code 
object.getName()}.
+          * @param  result       the collection where to add the object 
instantiated from the identifiers.
+          * @throws FactoryException if an error occurred while creating an 
object.
+          */
+         private void createFromIdentifiers(final 
AuthorityFactoryIdentifier.Type type, final IdentifiedObject object,
 -                final Iterable<Identifier> identifiers, final 
Set<IdentifiedObject> result) throws FactoryException
++                final Iterable<ReferenceIdentifier> identifiers, final 
Set<IdentifiedObject> result) throws FactoryException
+         {
 -            for (final Identifier identifier : identifiers) {
++            for (final ReferenceIdentifier identifier : identifiers) {
+                 final String authority = identifier.getCodeSpace();
+                 if (authority != null) {
+                     @SuppressWarnings("LocalVariableHidesMemberVariable")
+                     final var factory = (MultiAuthoritiesFactory) 
this.factory;
+                     final IdentifiedObject candidate;
+                     try {
+                         final var fid = 
AuthorityFactoryIdentifier.create(type, authority, identifier.getVersion());
+                         candidate = 
createAndFilter(factory.getAuthorityFactory(fid), identifier.getCode(), object);
+                     } catch (NoSuchAuthorityCodeException e) {
+                         // The identifier was not recognized. No problem, 
let's go on.
+                         exceptionOccurred(e);
+                         continue;
+                     }
+                     if (candidate != null) {
+                         result.add(candidate);
+                     }
+                 }
+             }
+         }
+ 
+         /**
+          * Creates objects approximately equal to the specified object by 
iterating over authority code candidates.
+          * This method is invoked by {@link #find(IdentifiedObject)} when the 
result was not already in the cache.
+          * First, this method tries to delegate to the factory specified by 
the name space of each identifier.
+          * If this quick search using declared identifiers did not worked, 
then this method delegates to every
+          * factories registered in the enclosing {@link 
MultiAuthoritiesFactory}, in iteration order.
           *
           * <p>This method shall <strong>not</strong> delegate the job to the 
parent class, as the default
           * implementation in the parent class is very inefficient. We need to 
delegate to the finders of
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index b94cbce82e,5c95acd0bc..e113272552
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@@ -53,9 -64,13 +64,13 @@@ import org.apache.sis.referencing.facto
  import org.apache.sis.referencing.factory.ConcurrentAuthorityFactory;
  import static 
org.apache.sis.metadata.privy.NameToIdentifier.Simplifier.ESRI_DATUM_PREFIX;
  
 +// Specific to the main and geoapi-3.1 branches:
 +import org.opengis.referencing.crs.GeneralDerivedCRS;
 +
+ // Specific to the geoapi-3.1 and geoapi-4.0 branches:
+ import org.opengis.referencing.crs.ParametricCRS;
+ import org.opengis.referencing.datum.ParametricDatum;
+ 
 -// Specific to the geoapi-4.0 branch:
 -import org.opengis.referencing.crs.DerivedCRS;
 -
  
  /**
   * An implementation of {@link IdentifiedObjectFinder} which scans over a 
smaller set of authority codes.
@@@ -319,14 -361,18 +361,18 @@@ crs:    if (isInstance(CoordinateRefere
               *     ORDER BY COORD_REF_SYS_CODE
               */
              final Condition filter;
 -            if (object instanceof DerivedCRS) {              // No need to 
use isInstance(Class, Object) from here.
 -                filter = dependencies("BASE_CRS_CODE", SingleCRS.class, 
((DerivedCRS) object).getBaseCRS(), true);
 +            if (object instanceof GeneralDerivedCRS) {              // No 
need to use isInstance(Class, Object) from here.
 +                filter = dependencies("BASE_CRS_CODE", 
CoordinateReferenceSystem.class, ((GeneralDerivedCRS) object).getBaseCRS(), 
true);
              } else if (object instanceof GeodeticCRS) {
-                 filter = dependencies("DATUM_CODE", GeodeticDatum.class, 
((GeodeticCRS) object).getDatum(), true);
+                 filter = dependencies("DATUM_CODE", GeodeticDatum.class, 
DatumOrEnsemble.asDatum((GeodeticCRS) object), true);
              } else if (object instanceof VerticalCRS) {
-                 filter = dependencies("DATUM_CODE", VerticalDatum.class, 
((VerticalCRS) object).getDatum(), true);
+                 filter = dependencies("DATUM_CODE", VerticalDatum.class, 
DatumOrEnsemble.asDatum((VerticalCRS) object), true);
              } else if (object instanceof TemporalCRS) {
-                 filter = dependencies("DATUM_CODE", TemporalDatum.class, 
((TemporalCRS) object).getDatum(), true);
+                 filter = dependencies("DATUM_CODE", TemporalDatum.class, 
DatumOrEnsemble.asDatum((TemporalCRS) object), true);
+             } else if (object instanceof ParametricCRS) {
+                 filter = dependencies("DATUM_CODE", ParametricDatum.class, 
DatumOrEnsemble.asDatum((ParametricCRS) object), true);
+             } else if (object instanceof EngineeringCRS) {
+                 filter = dependencies("DATUM_CODE", EngineeringDatum.class, 
DatumOrEnsemble.asDatum((EngineeringCRS) object), true);
              } else if (object instanceof SingleCRS) {
                  filter = dependencies("DATUM_CODE", Datum.class, ((SingleCRS) 
object).getDatum(), true);
              } else {
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
index 49c0195828,2622020443..835c89fb5e
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
@@@ -25,135 -26,196 +26,193 @@@ import org.opengis.referencing.datum.*
  import org.opengis.referencing.operation.*;
  import org.opengis.parameter.ParameterDescriptor;
  import org.apache.sis.referencing.privy.WKTKeywords;
- import org.apache.sis.util.CharSequences;
  
 -// Specific to the geoapi-4.0 branch:
 -import org.apache.sis.referencing.crs.DefaultGeocentricCRS;
 -
  
  /**
-  * Information about a specific table. This class uses the mixed-case variant 
of the <abbr>EPSG</abbr> table names.
-  * If needed, those names will be converted by {@link 
SQLTranslator#apply(String)} to the actual table names.
+  * Information (such as columns of particular interest) about a specific 
<abbr>EPSG</abbr> table.
+  * This class uses the mixed-case variant of the <abbr>EPSG</abbr> 
{@linkplain #table table names}.
+  * The {@link #values()} can be tested in preference order for finding the 
table of an object.
+  * Those tables are used by the {@link EPSGDataAccess#createObject(String)} 
method in order to
+  * detect which of the following methods should be invoked for a given code:
+  *
+  * {@link EPSGDataAccess#createCoordinateReferenceSystem(String)}
+  * {@link EPSGDataAccess#createCoordinateSystem(String)}
+  * {@link EPSGDataAccess#createDatum(String)}
+  * {@link EPSGDataAccess#createEllipsoid(String)}
+  * {@link EPSGDataAccess#createUnit(String)}
+  *
+  * <h4>Ambiguity</h4>
+  * As of ISO 19111:2019, we have no standard way to identify the geocentric 
case from a {@link Class} argument
+  * because the standard does not provide the {@code GeocentricCRS} interface. 
This implementation fallbacks on
 - * the <abbr>SIS</abbr>-specific geocentric <abbr>CRS</abbr> class. This 
special case is implemented in the
++ * the GeoAPI-specific geocentric <abbr>CRS</abbr> class. This special case 
is implemented in the
+  * {@link #appendWhere(EPSGDataAccess, Object, StringBuilder)} method.
   *
   * @author  Martin Desruisseaux (IRD, Geomatys)
   */
- final class TableInfo {
+ enum TableInfo {
      /**
-      * The item used for coordinate reference systems.
+      * Information about the "Coordinate Reference System" table.
       */
-     static final TableInfo CRS;
+     CRS(CoordinateReferenceSystem.class,
+             "\"Coordinate Reference System\"",
+             "COORD_REF_SYS_CODE",
+             "COORD_REF_SYS_NAME",
+             "COORD_REF_SYS_KIND",
 -            new Class<?>[] { ProjectedCRS.class,   GeographicCRS.class,   
DefaultGeocentricCRS.class,
++            new Class<?>[] { ProjectedCRS.class,   GeographicCRS.class,   
GeocentricCRS.class,
+                              VerticalCRS.class,    CompoundCRS.class,     
EngineeringCRS.class,
+                              DerivedCRS.class,     TemporalCRS.class,     
ParametricCRS.class},     // See comment below
+             new String[]   {"projected",          "geographic 2D",       
"geocentric",              // 3D case handled below
+                             "vertical",           "compound",            
"engineering",
+                             "derived",            "temporal",            
"parametric"},             // See comment below
+             "SHOW_CRS", true),
+             /*
+              * Above declaration could omit Derived, Temporal and Parametric 
cases because they are not defined
+              * by the EPSG repository (at least as of version 8.9). In 
particular we are not sure if EPSG would
+              * chose to use "time" or "temporal". However, omitting those 
types slow down a lot the search for
+              * CRS matching an existing one (even if it still work).
+              */
  
      /**
-      * The item used for datums.
+      * Information about the "Datum" table.
       */
-     static final TableInfo DATUM;
+     DATUM(Datum.class,
+             "\"Datum\"",
+             "DATUM_CODE",
+             "DATUM_NAME",
+             "DATUM_TYPE",
+             new Class<?>[] { DatumEnsemble.class,  // Need to be first 
because Apache SIS uses as mixin interface.
+                              GeodeticDatum.class,  VerticalDatum.class,   
EngineeringDatum.class,
+                              TemporalDatum.class,  ParametricDatum.class},
+             new String[]   {"ensemble",
+                             "geodetic",           "vertical",            
"engineering",
+                             "temporal",           "parametric"},         // 
Same comment as in the CRS case above.
+             null, true),
  
      /**
-      * The item used for ellipsoids.
+      * Information about the "Conventional RS" table.
+      * This enumeration usually needs to be ignored because the current type 
is too generic.
+      *
+      * @see #isSpecificEnough()
       */
-     static final TableInfo ELLIPSOID;
+     CONVENTIONAL_RS(IdentifiedObject.class,
+             "\"Conventional RS\"",
+             "CONVENTIONAL_RS_CODE",
+             "CONVENTIONAL_RS_NAME",
+             null, null, null, null, false),
  
      /**
-      * List of tables and columns to test for existence of codes values.
-      * Those tables are used by the {@link 
EPSGDataAccess#createObject(String)} method
-      * in order to detect which of the following methods should be invoked 
for a given code:
-      *
-      * {@link EPSGDataAccess#createCoordinateReferenceSystem(String)}
-      * {@link EPSGDataAccess#createCoordinateSystem(String)}
-      * {@link EPSGDataAccess#createDatum(String)}
-      * {@link EPSGDataAccess#createEllipsoid(String)}
-      * {@link EPSGDataAccess#createUnit(String)}
+      * Information about the "Ellipsoid" table.
+      */
+     ELLIPSOID(Ellipsoid.class,
+             "\"Ellipsoid\"",
+             "ELLIPSOID_CODE",
+             "ELLIPSOID_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Prime Meridian" table.
+      */
+     PRIME_MERIDIAN(PrimeMeridian.class,
+             "\"Prime Meridian\"",
+             "PRIME_MERIDIAN_CODE",
+             "PRIME_MERIDIAN_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Coordinate_Operation" table.
+      */
+     OPERATION(CoordinateOperation.class,
+             "\"Coordinate_Operation\"",
+             "COORD_OP_CODE",
+             "COORD_OP_NAME",
+             "COORD_OP_TYPE",
+             new Class<?>[] { Conversion.class, Transformation.class},
+             new String[]   {"conversion",     "transformation"},
+             "SHOW_OPERATION", true),
+ 
+     /**
+      * Information about the "Coordinate_Operation Method" table.
+      */
+     METHOD(OperationMethod.class,
+             "\"Coordinate_Operation Method\"",
+             "COORD_OP_METHOD_CODE",
+             "COORD_OP_METHOD_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Coordinate_Operation Parameter" table.
+      */
+     PARAMETER(ParameterDescriptor.class,
+             "\"Coordinate_Operation Parameter\"",
+             "PARAMETER_CODE",
+             "PARAMETER_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Extent" table.
+      */
+     EXTENT(Extent.class,
+             "\"Extent\"",
+             "EXTENT_CODE",
+             "EXTENT_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Coordinate System" table.
+      */
+     CS(CoordinateSystem.class,
+             "\"Coordinate System\"",
+             "COORD_SYS_CODE",
+             "COORD_SYS_NAME",
+             "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},
+             new String[]   {WKTKeywords.Cartesian,  WKTKeywords.ellipsoidal,  
WKTKeywords.vertical,  WKTKeywords.linear,
+                             WKTKeywords.spherical,  WKTKeywords.polar,        
WKTKeywords.cylindrical,
+                             WKTKeywords.temporal,   WKTKeywords.parametric,   
WKTKeywords.affine},      // Same comment as in the CRS case above.
+             null, false),
+ 
+     /**
+      * Information about the "Coordinate Axis" table.
+      */
+     AXIS(CoordinateSystemAxis.class,
+             "\"Coordinate Axis\" AS CA INNER JOIN \"Coordinate Axis Name\" AS 
CAN " +
+                                 "ON 
CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE",
+             "COORD_AXIS_CODE",
+             "COORD_AXIS_NAME",
+             null, null, null, null, false),
+ 
+     /**
+      * Information about the "Unit of Measure" table.
+      */
+     UNIT(Unit.class,
+             "\"Unit of Measure\"",
+             "UOM_CODE",
+             "UNIT_OF_MEAS_NAME",
+             null, null, null, null, false);
+ 
+     /**
+      * Types to consider as synonymous for searching purposes. This map 
exists for historical reasons,
+      * because dynamic datum and datum ensemble did not existed in older 
<abbr>ISO</abbr> 19111 standards.
+      * If an object to search is "geodetic", there is a possibility that it 
is defined in the old way and
+      * actually appears as a "dynamic geodetic" or "ensemble" in the 
<abbr>EPSG</abbr> geodetic dataset.
       *
-      * The order is significant: it is the key for a {@code switch} statement.
+      * <p>The "geographic 3D" case is handled in a special way. It is 
considered as a synonymous of
+      * "geographic 2D" only when we don't know the number of dimensions.</p>
+      */
+     private static final Map<String, String[]> SYNONYMOUS_TYPES = Map.of(
+             "geodetic",      new String[] {"dynamic geodetic", "ensemble"},
+             "geographic 2D", new String[] {"geographic 3D"});
+ 
+     /**
+      * Types to replace by specialized types when the user-specified instance 
implements a mixin interface.
+      * For example, {@link DynamicReferenceFrame} means to not search for any 
geodetic datum, but only for
+      * dynamic geodetic datum.
       */
-     @SuppressWarnings("deprecation")
-     static final TableInfo[] EPSG = {
-         CRS = new TableInfo(CoordinateReferenceSystem.class,
-                 "\"Coordinate Reference System\"",
-                 "COORD_REF_SYS_CODE",
-                 "COORD_REF_SYS_NAME",
-                 "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
-                 new String[]   {"projected",          "geographic",          
"geocentric",
-                                 "vertical",           "compound",            
"engineering",
-                                 "derived",            "temporal",            
"parametric"},             // See comment below
-                 "SHOW_CRS", true),
-                 /*
-                  * Above declaration could omit Derived, Temporal and 
Parametric cases because they are not defined
-                  * by the EPSG repository (at least as of version 8.9). In 
particular we are not sure if EPSG would
-                  * chose to use "time" or "temporal". However, omitting those 
types slow down a lot the search for
-                  * CRS matching an existing one (even if it still work).
-                  */
- 
-         new TableInfo(CoordinateSystem.class,
-                 "\"Coordinate System\"",
-                 "COORD_SYS_CODE",
-                 "COORD_SYS_NAME",
-                 "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},
-                 new String[]   {WKTKeywords.Cartesian,  
WKTKeywords.ellipsoidal,  WKTKeywords.vertical,  WKTKeywords.linear,
-                                 WKTKeywords.spherical,  WKTKeywords.polar,    
    WKTKeywords.cylindrical,
-                                 WKTKeywords.temporal,   
WKTKeywords.parametric,   WKTKeywords.affine},      // Same comment as in the 
CRS case above.
-                 null, false),
- 
-         new TableInfo(CoordinateSystemAxis.class,
-                 "\"Coordinate Axis\" AS CA INNER JOIN \"Coordinate Axis 
Name\" AS CAN " +
-                                     "ON 
CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE",
-                 "COORD_AXIS_CODE",
-                 "COORD_AXIS_NAME",
-                 null, null, null, null, false),
- 
-         DATUM = new TableInfo(Datum.class,
-                 "\"Datum\"",
-                 "DATUM_CODE",
-                 "DATUM_NAME",
-                 "DATUM_TYPE",
-                 new Class<?>[] { GeodeticDatum.class,  VerticalDatum.class,   
EngineeringDatum.class,
-                                  TemporalDatum.class,  ParametricDatum.class},
-                 new String[]   {"geodetic",           "vertical",            
"engineering",
-                                 "temporal",           "parametric"},         
// Same comment as in the CRS case above.
-                 null, true),
- 
-         ELLIPSOID = new TableInfo(Ellipsoid.class,
-                 "\"Ellipsoid\"",
-                 "ELLIPSOID_CODE",
-                 "ELLIPSOID_NAME",
-                 null, null, null, null, false),
- 
-         new TableInfo(PrimeMeridian.class,
-                 "\"Prime Meridian\"",
-                 "PRIME_MERIDIAN_CODE",
-                 "PRIME_MERIDIAN_NAME",
-                 null, null, null, null, false),
- 
-         new TableInfo(CoordinateOperation.class,
-                 "\"Coordinate_Operation\"",
-                 "COORD_OP_CODE",
-                 "COORD_OP_NAME",
-                 "COORD_OP_TYPE",
-                 new Class<?>[] { Conversion.class, Transformation.class},
-                 new String[]   {"conversion",     "transformation"},
-                 "SHOW_OPERATION", true),
- 
-         new TableInfo(OperationMethod.class,
-                 "\"Coordinate_Operation Method\"",
-                 "COORD_OP_METHOD_CODE",
-                 "COORD_OP_METHOD_NAME",
-                 null, null, null, null, false),
- 
-         new TableInfo(ParameterDescriptor.class,
-                 "\"Coordinate_Operation Parameter\"",
-                 "PARAMETER_CODE",
-                 "PARAMETER_NAME",
-                 null, null, null, null, false),
- 
-         new TableInfo(Unit.class,
-                 "\"Unit of Measure\"",
-                 "UOM_CODE",
-                 "UNIT_OF_MEAS_NAME",
-                 null, null, null, null, false),
-     };
+     private static final Map<String, String> DYNAMIC_TYPES = Map.of(
+             "geodetic", "dynamic geodetic");
+             // We would expect a "dynamic vertical" as well, but we don't see 
it yet in EPSG database.
  
      /**
       * The class of object to be created (usually a GeoAPI interface).
@@@ -287,22 -347,63 +344,63 @@@
      }
  
      /**
-      * Appends a {@code WHERE} clause together with a condition for searching 
the most specific subtype,
-      * if such condition can be added. The clause appended by this method 
looks like the following example
-      * (details may vary because of enumeration values):
+      * Appends a {@code WHERE} clause together with a condition for searching 
the most specific subtype.
+      * The clause appended by this method looks like the following example:
       *
       * {@snippet lang="sql" :
-      *     WHERE COORD_REF_SYS_KIND LIKE 'geographic%' AND
+      *     WHERE (COORD_REF_SYS_KIND = 'geographic 2D' OR COORD_REF_SYS_KIND 
= 'geographic 3D') AND
       *     }
       *
-      * The caller shall add at least one condition after this method call.
+      * The <abbr>SQL</abbr> fragment will have a trailing {@code WHERE} or 
{@code AND} keyword.
+      * Therefore, the caller shall add at least one condition after this 
method call.
+      *
+      * <h4>Object type</h4>
+      * The {@code object} argument shall be one of the following types:
+      *
+      * <ul>
+      *   <li>An {@link IdentifiedObject} instance.</li>
+      *   <li>The {@link Class} of an {@code IdentifiedObject}. It may be an 
implementation class.</li>
+      *   <li>An opaque key computed by {@link 
#toCacheKey(IdentifiedObject)}.</li>
+      * </ul>
       *
-      * @param  factory   the factory which is writing a <abbr>SQL</abbr> 
statement.
-      * @param  userType  the type specified by the user.
-      * @param  buffer    where to append the {@code WHERE} clause.
-      * @return the subtype, or {@link #type} if no subtype was found.
+      * This method returns a generalization of the {@code object} argument: 
either a GeoAPI interface,
+      * or {@code object} if it was a cache key computed by {@link 
#toCacheKey(IdentifiedObject)}.
+      *
+      * @param  factory  the factory which is writing a <abbr>SQL</abbr> 
statement.
+      * @param  object   the instance, class or cache key to search in the 
database.
+      * @param  buffer   where to append the {@code WHERE} clause.
+      * @return the {@code object} argument, potentially generalized.
       */
-     final Class<?> where(final EPSGDataAccess factory, final Class<?> 
userType, final StringBuilder buffer) {
+     final Object appendWhere(final EPSGDataAccess factory, final Object 
object, final StringBuilder buffer) {
+         final int dimension;            // 0 if not applicable. This is 
applicable only to `GeographicCRS`.
+         final Class<?> userType;
+         if (object instanceof Integer) {
+             dimension = (Integer) object;
+             userType  = GeographicCRS.class;
+         } else if (object instanceof Class<?>) {
+             userType  = (Class<?>) object;
+             dimension = 0;
+         } else if (object instanceof GeodeticCRS) {
+             final CoordinateSystem cs = ((GeodeticCRS) 
object).getCoordinateSystem();
+             if (cs instanceof EllipsoidalCS) {
+                 userType  = GeographicCRS.class;
+                 dimension = cs.getDimension();      // Intentionally 
restricted to this specific case.
+             } else {
+                 if (cs instanceof CartesianCS || cs instanceof SphericalCS) {
 -                    userType = DefaultGeocentricCRS.class;
++                    userType = GeocentricCRS.class;
+                 } else {
+                     userType = object.getClass();
+                 }
+                 dimension = 0;
+             }
+         } else {
+             userType  = object.getClass();
+             dimension = 0;
+         }
+         /*
+          * Above code decomposed the given `object`.
+          * The rest of this method builds the SQL.
+          */
          buffer.append(" WHERE ");
          if (typeColumn != null) {
              for (int i=0; i<subTypes.length; i++) {
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/RTreeNode.java
index 6b699e66f1,28ee86f03f..39919400ee
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/RTreeNode.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/RTreeNode.java
@@@ -250,8 -250,8 +250,8 @@@ detach: for (RTreeNode next; node != nu
                  final CoordinateReferenceSystem crs = 
node.getCoordinateReferenceSystem();
                  if (common == null) {
                      common = crs;
-                 } else if (crs != null && 
!Utilities.equalsIgnoreMetadata(common, crs)) {
+                 } else if (crs != null && !CRS.equivalent(common, crs)) {
 -                    throw new 
MismatchedCoordinateMetadataException(Errors.format(Errors.Keys.MismatchedCRS));
 +                    throw new 
MismatchedReferenceSystemException(Errors.format(Errors.Keys.MismatchedCRS));
                  }
              }
          }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
index 2911c9a540,88a6330dfa..e2aacdd5c1
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
@@@ -148,8 -148,9 +148,11 @@@ public final class VerticalDatumTypes 
                  case ORTHOMETRIC: return 2001;      // CS_VD_Orthometric
                  case ELLIPSOIDAL: return 2002;      // CS_VD_Ellipsoidal
                  case BAROMETRIC:  return 2003;      // 
CS_VD_AltitudeBarometric
-                 case "GEOIDAL":   return 2005;      // CS_VD_GeoidModelDerived
++                case "GEOIDAL":
+                 case "GEOID":     return 2005;      // CS_VD_GeoidModelDerived
+                 case "LEVELLING": // From ISO: "adjustment of a levelling 
network fixed to one or more tide gauges".
 -                case "TIDAL":     return 2006;      // CS_VD_Depth
++                case "TIDAL":
 +                case "DEPTH":     return 2006;      // CS_VD_Depth
              }
          }
          return 2000;
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index 854470625e,3c45c52ffc..080993c5d5
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@@ -328,17 -324,11 +328,16 @@@ class CoordinateOperationRegistry 
              }
              codes = new ArrayList<>();
              codeFinder.setIgnoringAxes(true);
-             codeFinder.setSearchDomain(isEasySearch(crs)
-                     ? IdentifiedObjectFinder.Domain.EXHAUSTIVE_VALID_DATASET
-                     : IdentifiedObjectFinder.Domain.VALID_DATASET);
+             codeFinder.setSearchDomain(isEasySearch(crs) ? 
IdentifiedObjectFinder.Domain.EXHAUSTIVE_VALID_DATASET
+                                                          : 
IdentifiedObjectFinder.Domain.VALID_DATASET);
              int matchCount = 0;
              try {
 -                final Citation authority = registry.getAuthority();
 +                final Citation authority;
 +                try {
 +                    authority = registry.getAuthority();
 +                } catch (BackingStoreException e) {
 +                    throw e.unwrapOrRethrow(FactoryException.class);
 +                }
                  for (final IdentifiedObject candidate : codeFinder.find(crs)) 
{
                      final Identifier identifier = 
IdentifiedObjects.getIdentifier(candidate, authority);
                      if (identifier != null) {
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java
index 05f765e5cb,8a202cff50..77a1abd1b3
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java
@@@ -487,10 -446,8 +485,10 @@@ public class DefaultOperationMethod ext
                      break;
                  }
              }
-             final OperationMethod that = (OperationMethod) object;
+             final var that = (OperationMethod) object;
 -            return Utilities.deepEquals(getParameters(), 
that.getParameters(), mode);
 +            return Objects.equals(getSourceDimensions(), 
that.getSourceDimensions()) &&
 +                   Objects.equals(getTargetDimensions(), 
that.getTargetDimensions()) &&
 +                   Utilities.deepEquals(getParameters(), 
that.getParameters(), mode);
          }
          return false;
      }
diff --cc 
endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java
index ed337dad42,88c1a98f29..90f79f248a
--- 
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
@@@ -29,9 -32,9 +32,9 @@@ import org.opengis.referencing.operatio
   *   <li>{@link #STRICT}          – All attributes of the compared objects 
shall be strictly equal.</li>
   *   <li>{@link #BY_CONTRACT}     – Only the attributes published in the 
interface contract need to be compared.</li>
   *   <li>{@link #IGNORE_METADATA} – Only the attributes relevant to the 
object functionality are compared.</li>
-  *   <li>{@link #APPROXIMATE}     – Only the attributes relevant to the 
object functionality are compared,
-  *                                  with some tolerance threshold on 
numerical values.</li>
-  *   <li>{@link #ALLOW_VARIANT}   – For objects not really equal but related 
(e.g. CRS using different axis order).</li>
 - *   <li>{@link #COMPATIBILITY}      – Like {@code IGNORE_METADATA}, but 
ignore also some structural changes for historical reasons.</li>
++ *   <li>{@link #COMPATIBILITY}   – Like {@code IGNORE_METADATA}, but ignore 
also some structural changes for historical reasons.</li>
+  *   <li>{@link #APPROXIMATE}     – Like {@code COMPATIBILITY}, with some 
tolerance threshold on numerical values.</li>
+  *   <li>{@link #ALLOW_VARIANT}   – Objects not really equal but related 
(e.g., <abbr>CRS</abbr> using different axis order).</li>
   *   <li>{@link #DEBUG}           – Special mode for figuring out why two 
objects expected to be equal are not.</li>
   * </ol>
   *

Reply via email to