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

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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 02e6ffe626 Add an implementation of `RegisterOperations` (a new 
interface added in ISO 19111:2019).
02e6ffe626 is described below

commit 02e6ffe626b5eaae5747b80a54567bb01982e9e3
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Jun 28 20:09:55 2024 +0200

    Add an implementation of `RegisterOperations` (a new interface added in ISO 
19111:2019).
---
 .../geometry/wrapper/SpatialOperationContext.java  |   4 +-
 .../org/apache/sis/util/iso/AbstractFactory.java   |  22 +-
 .../main/module-info.java                          |   3 +
 .../main/org/apache/sis/referencing/CRS.java       |   3 +-
 .../sis/referencing/MultiRegisterOperations.java   | 445 +++++++++++++++++++++
 .../factory/MultiAuthoritiesFactory.java           |  22 +-
 .../operation/CoordinateOperationRegistry.java     |   5 +-
 .../DefaultCoordinateOperationFactory.java         |   4 +-
 .../factory/GeodeticObjectFactoryTest.java         |  18 +-
 .../referencing/geoapi/AuthorityFactoryTest.java   |   3 +-
 geoapi/snapshot                                    |   2 +-
 11 files changed, 513 insertions(+), 18 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
index 208b00e466..7fa87083ee 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
@@ -357,8 +357,8 @@ select: if (commonCRS == null) {
 
         /** Creates the {@link #INSTANCE} singleton. */
         private Projector() throws FactoryException {
-            final ReferencingFactoryContainer f = new 
ReferencingFactoryContainer();
-            method = 
f.getCoordinateOperationFactory().getOperationMethod("Mercator_2SP");
+            final var f = new ReferencingFactoryContainer();
+            method = f.findOperationMethod("Mercator_2SP");
             cartCS = f.getStandardProjectedCS();
             name   = Map.of(DefaultConversion.NAME_KEY,
                             new ImmutableIdentifier(Citations.SIS, "SIS", 
"Mercator for geometry"));
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/AbstractFactory.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/AbstractFactory.java
index 26b8a18ef3..11a32493d4 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/AbstractFactory.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/AbstractFactory.java
@@ -19,13 +19,14 @@ package org.apache.sis.util.iso;
 import org.opengis.util.Factory;
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.metadata.simple.SimpleCitation;
+import org.apache.sis.util.privy.Strings;
 
 
 /**
  * Base class of factories provided in the Apache SIS library.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.3
+ * @version 1.5
  * @since   0.3
  */
 public abstract class AbstractFactory implements Factory {
@@ -55,4 +56,23 @@ public abstract class AbstractFactory implements Factory {
         }
         return null;
     }
+
+    /**
+     * Returns a string representation of this factory for debugging purposes.
+     * This string representation may change in any future version of Apache 
SIS.
+     *
+     * @return a string representation of this factory.
+     *
+     * @since 1.5
+     */
+    @Override
+    public String toString() {
+        final var args = new Object[2];
+        Citation c = getVendor();
+        if (c != null) {
+            args[0] = "vendor";
+            args[1] = c.getTitle();
+        }
+        return Strings.toString(getClass(), args);
+    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/module-info.java 
b/endorsed/src/org.apache.sis.referencing/main/module-info.java
index c19d16bee7..ba29bb0806 100644
--- a/endorsed/src/org.apache.sis.referencing/main/module-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/module-info.java
@@ -33,6 +33,9 @@ module org.apache.sis.referencing {
     provides org.apache.sis.metadata.sql.privy.Initializer
         with org.apache.sis.referencing.internal.DatabaseListener;
 
+    provides org.opengis.referencing.RegisterOperations
+        with org.apache.sis.referencing.MultiRegisterOperations;
+
     provides org.opengis.referencing.crs.CRSFactory
         with org.apache.sis.referencing.factory.GeodeticObjectFactory;
 
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 8003fd784a..7cb26286f0 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
@@ -1576,8 +1576,7 @@ check:  while (lower != 0 || upper != dimension) {
      * @return the system-wide authority factory used by SIS for the given 
authority.
      * @throws FactoryException if no factory can be returned for the given 
authority.
      *
-     * @see #forCode(String)
-     * @see org.apache.sis.referencing.factory.MultiAuthoritiesFactory
+     * @see MultiRegisterOperations
      *
      * @since 0.7
      */
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
new file mode 100644
index 0000000000..6bf7628dbd
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/MultiRegisterOperations.java
@@ -0,0 +1,445 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+import java.util.Iterator;
+import java.util.AbstractSet;
+import java.util.Objects;
+import java.util.Optional;
+import org.opengis.util.Factory;
+import org.opengis.util.FactoryException;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.AuthorityFactory;
+import org.opengis.referencing.RegisterOperations;
+import org.opengis.referencing.crs.SingleCRS;
+import org.opengis.referencing.crs.CRSFactory;
+import org.opengis.referencing.crs.CRSAuthorityFactory;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.cs.CSFactory;
+import org.opengis.referencing.cs.CSAuthorityFactory;
+import org.opengis.referencing.datum.DatumFactory;
+import org.opengis.referencing.datum.DatumAuthorityFactory;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.CoordinateOperationFactory;
+import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
+import org.apache.sis.referencing.factory.MultiAuthoritiesFactory;
+import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException;
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
+import org.apache.sis.util.Utilities;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.iso.AbstractFactory;
+
+
+/**
+ * Finds <abbr>CRS</abbr>s or coordinate operations in one or many geodetic 
registries.
+ * Each {@code MultiRegisterOperations} instance can narrow the search to a 
single registry,
+ * a specific version of that registry, or to a domain of validity.
+ * Each instance is immutable and thread-safe.
+ *
+ * <p>This class delegates its work to {@linkplain CRS#forCode(String) static 
methods} or to
+ * {@link MultiAuthoritiesFactory}. It does not provide new services compared 
to the above,
+ * but provides a more high-level <abbr>API</abbr> with the most important 
registry-based
+ * services in a single place. {@link RegisterOperations} can also be used as 
en entry point,
+ * with accesses to the low-level <abbr>API</abbr> granted by {@link 
#getFactory(Class)}.</p>
+ *
+ * <h2>User-defined geodetic registries</h2>
+ * User-defined authorities can be added to the SIS environment by creating 
{@link CRSAuthorityFactory}
+ * implementations with a public no-argument constructor or a public static 
{@code provider()} method,
+ * and declaring the name of those classes in the {@code module-info.java} 
file as a provider of the
+ * {@code org.opengis.referencing.crs.CRSAuthorityFactory} service.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.5
+ * @since   1.5
+ */
+public class MultiRegisterOperations extends AbstractFactory implements 
RegisterOperations {
+    /**
+     * Types of factories supported by this implementation.
+     * A value of {@code true} means that the factory is an authority factory.
+     * A value of {@code false} means that the factory is an object factory.
+     *
+     * @see #getFactory(Class)
+     */
+    private static final Map<Class<?>, Boolean> FACTORY_TYPES = Map.of(
+            CoordinateOperationAuthorityFactory.class, Boolean.TRUE,
+            DatumAuthorityFactory.class, Boolean.TRUE,
+            CRSAuthorityFactory.class,   Boolean.TRUE,
+            CSAuthorityFactory.class,    Boolean.TRUE,
+            DatumFactory.class,          Boolean.FALSE,
+            CRSFactory.class,            Boolean.FALSE,
+            CSFactory.class,             Boolean.FALSE);
+
+    /**
+     * The authority of the <abbr>CRS</abbr> and coordinate operations to 
search,
+     * or {@code null} for all authorities. In the latter case, the authority 
must
+     * be specified in the code, for example {@code "EPSG:4326"} instead of 
"4326".
+     *
+     * @see #withAuthority(String)
+     */
+    private final String authority;
+
+    /**
+     * Version of the registry to use, or {@code null} for the default version.
+     * Can be non-null only if {@link #authority} is also non-null.
+     * If null, the default version is usually the latest one.
+     *
+     * @see #withVersion(String)
+     */
+    private final String version;
+
+    /**
+     * The area of interest for coordinate operations, or {@code null} for the 
whole world.
+     *
+     * @see #withAreaOfInterest(GeographicBoundingBox)
+     */
+    private final GeographicBoundingBox areaOfInterest;
+
+    /**
+     * The authority factory to use for extracting <abbr>CRS</abbr> instances, 
or {@code null} if no
+     * authority has been specified. In the latter case, {@link CRS} static 
methods should be used.
+     * In Apache SIS implementation, this is also an {@link 
CoordinateOperationAuthorityFactory}.
+     *
+     * @see #findCoordinateReferenceSystem(String)
+     * @see #findCoordinateOperation(String)
+     */
+    private final CRSAuthorityFactory crsFactory;
+
+    /**
+     * The singleton instance for all authorities in their default versions, 
with no <abbr>AOI</abbr>.
+     *
+     * @see #provider()
+     */
+    private static final MultiRegisterOperations DEFAULT = new 
MultiRegisterOperations();
+
+    /**
+     * Returns an instance which will search <abbr>CRS</abbr> definitions in 
all registries that are known to SIS.
+     * Because this instance is not for a specific registry, the authority 
will need to be part of the {@code code}
+     * argument given to {@code create(String)} methods. For example, {@code 
"EPSG:4326"} instead of {@code "4326"}.
+     * The registry can be made implicit by a call to {@link 
#withAuthority(String)}.
+     *
+     * @return the default instance for all registries known to SIS.
+     */
+    public static MultiRegisterOperations provider() {
+        return DEFAULT;
+    }
+
+    /**
+     * Creates an instance which will search <abbr>CRS</abbr> definitions in 
all registries that are known to SIS.
+     *
+     * @see #provider()
+     */
+    private MultiRegisterOperations() {
+        authority      = null;
+        version        = null;
+        crsFactory     = null;
+        areaOfInterest = null;
+    }
+
+    /**
+     * Creates an instance with the same register than the given instance, but 
a different <abbr>AOI</abbr>.
+     *
+     * @param source          the register from which to copy the authority 
and version.
+     * @param areaOfInterest  the new area of interest (<abbr>AOI</abbr>), or 
{@code null} if none.
+     *
+     * @see #withAreaOfInterest(GeographicBoundingBox)
+     */
+    protected MultiRegisterOperations(final MultiRegisterOperations source, 
final GeographicBoundingBox areaOfInterest) {
+        authority  = source.authority;
+        version    = source.version;
+        crsFactory = source.crsFactory;
+        this.areaOfInterest = areaOfInterest;
+    }
+
+    /**
+     * Creates an instance which will use the registry of the specified 
authority, optionally at a specified version.
+     *
+     * @param source     the register from which to copy the area of interest.
+     * @param authority  identification of the registry to use (e.g., "EPSG").
+     * @param version    the registry version to use, or {@code null} for the 
default version.
+     * @throws NoSuchAuthorityFactoryException if the specified registry has 
not been found.
+     *
+     * @see #withAuthority(String)
+     * @see #withVersion(String)
+     */
+    protected MultiRegisterOperations(final MultiRegisterOperations source, 
final String authority, final String version)
+            throws NoSuchAuthorityFactoryException
+    {
+        this.authority      = Objects.requireNonNull(authority);
+        this.version        = version;
+        this.areaOfInterest = source.areaOfInterest;
+        crsFactory = 
AuthorityFactories.ALL.getAuthorityFactory(CRSAuthorityFactory.class, 
authority, version);
+    }
+
+    /**
+     * Returns the <abbr>CRS</abbr> authority factory.
+     */
+    private CRSAuthorityFactory crsFactory() {
+        return (crsFactory != null) ? crsFactory : AuthorityFactories.ALL;
+    }
+
+    /**
+     * Returns the organization or party responsible for definition and 
maintenance of the register.
+     * If an authority has been specified by a call to {@link 
#withAuthority(String)}, then this method
+     * returns that authority. Otherwise, this method returns {@code null}.
+     *
+     * @return the organization responsible for definitions in the registry, 
or {@code null} if none or many.
+     *
+     * @see MultiAuthoritiesFactory#getAuthority()
+     */
+    @Override
+    public Citation getAuthority() {
+        return crsFactory().getAuthority();
+    }
+
+    /**
+     * Returns an instance for a geodetic registry of the specified authority, 
such as "EPSG".
+     * If a {@linkplain #withVersion(String) version number was specified} 
previously, that version is cleared.
+     * If an area of interest was specified, the same area of interest is 
reused.
+     *
+     * <h2>User-defined geodetic registries</h2>
+     * A user-defined authority can be specified if the implementation is 
declared in a {@code module-info}
+     * file as a {@link CRSAuthorityFactory} service. See class javadoc for 
more information.
+     *
+     * @param  newValue  the desired authority, or {@code null} for all of 
them.
+     * @return register operations for the specified authority.
+     * @throws NoSuchAuthorityFactoryException if the given authority is 
unknown to SIS.
+     *
+     * @see CRS#getAuthorityFactory(String)
+     */
+    public MultiRegisterOperations withAuthority(final String newValue) throws 
NoSuchAuthorityFactoryException {
+        if (version == null && Objects.equals(authority, newValue)) {
+            return this;
+        } else if (newValue == null) {
+            return DEFAULT.withAreaOfInterest(areaOfInterest);
+        } else {
+            return new MultiRegisterOperations(this, newValue, null);
+        }
+    }
+
+    /**
+     * Returns an instance for the specified version of the geodetic registry.
+     * A non-null authority must have been {@linkplain #withAuthority(String) 
specified} before to invoke this method.
+     * If an area of interest was specified, the same area of interest is 
reused.
+     *
+     * @param  newValue  the desired version, or {@code null} for the default 
version.
+     * @return register operations for the specified version of the geodetic 
registry.
+     * @throws IllegalStateException if the version is non-null and no 
authority has been specified previously.
+     * @throws NoSuchAuthorityFactoryException if the given version is unknown 
to SIS.
+     */
+    public MultiRegisterOperations withVersion(final String newValue) throws 
NoSuchAuthorityFactoryException {
+        if (Objects.equals(version, newValue)) {
+            return this;
+        } else if (newValue == null && authority == null) {
+            return DEFAULT.withAreaOfInterest(areaOfInterest);
+        } else if (authority != null) {
+            return new MultiRegisterOperations(this, authority, newValue);
+        } else {
+            throw new 
IllegalStateException(Errors.format(Errors.Keys.MissingValueForProperty_1, 
"authority"));
+        }
+    }
+
+    /**
+     * Returns an instance for the specified area of interest 
(<abbr>AOI</abbr>).
+     * The area of interest is used for filtering coordinate operations between
+     * a {@linkplain #findCoordinateOperations between a pair of CRSs}.
+     *
+     * @param  newValue  the desired area of interest, or {@code null} for the 
world.
+     * @return register operations for the specified area of interest.
+     */
+    public MultiRegisterOperations withAreaOfInterest(final 
GeographicBoundingBox newValue) {
+        if (Objects.equals(areaOfInterest, newValue)) {
+            return this;
+        } else if (newValue == null && authority == null && version == null) {
+            return DEFAULT;
+        } else {
+            return new MultiRegisterOperations(this, newValue);
+        }
+    }
+
+    /**
+     * Returns the set of authority codes for objects of the given type.
+     * The {@code type} argument specifies the base type of identified objects.
+     * For example, {@code CoordinateReferenceSystem.class} is for requesting 
the <abbr>CRS</abbr> codes.
+     *
+     * <h4>Limitations</h4>
+     * In the current implementation, codes are filtered by authority and 
registry version,
+     * but not for the area of interest.
+     *
+     * @param  type  the type of referencing object for which to get authority 
codes.
+     * @return the set of authority codes for referencing objects of the given 
type.
+     * @throws FactoryException if access to the underlying database failed.
+     */
+    @Override
+    public Set<String> getAuthorityCodes(Class<? extends IdentifiedObject> 
type) throws FactoryException {
+        return crsFactory().getAuthorityCodes(type);
+    }
+
+    /**
+     * Returns a textual description of the object corresponding to a code.
+     * The description may be used in graphical user interfaces.
+     *
+     * @param  type  the type of object for which to get a description.
+     * @param  code  value allocated by the authority for an object of the 
given type.
+     * @return a description of the object, or empty if the object has no 
description.
+     * @throws NoSuchAuthorityCodeException if the specified {@code code} was 
not found.
+     * @throws FactoryException if the query failed for some other reason.
+     */
+    @Override
+    public Optional<InternationalString> getDescriptionText(Class<? extends 
IdentifiedObject> type, String code)
+            throws FactoryException
+    {
+        return crsFactory().getDescriptionText(type, code);
+    }
+
+    /**
+     * Extracts <abbr>CRS</abbr> details from the registry. If this {@code 
RegisterOperations} has not
+     * been restricted to a specific authority by a call to {@link 
#withAuthority(String)}, then the
+     * given code must contain the authority (e.g., {@code "EPSG:4326"} 
instead of {@code "4326"}.
+     * Otherwise, this method delegates to {@link CRS#forCode(jString)}.
+     *
+     * <p>By default, this method recognizes the {@code "EPSG"} and {@code 
"OGC"} authorities.
+     * In the {@code "EPSG"} case, whether the full set of EPSG codes is 
supported or not depends
+     * on whether a {@linkplain org.apache.sis.referencing.factory.sql 
connection to the database}
+     * can be established. If no connection can be established, then this 
method uses a small embedded
+     * EPSG factory containing at least the CRS defined in the {@link 
#forCode(String)} method javadoc.</p>
+     *
+     * @param  code  <abbr>CRS</abbr> identifier allocated by the authority.
+     * @return the <abbr>CRS</abbr> for the given authority code.
+     * @throws NoSuchAuthorityCodeException if the specified {@code code} was 
not found.
+     * @throws FactoryException if the search failed for some other reason.
+     *
+     * @see CRS#forCode(String)
+     */
+    @Override
+    public CoordinateReferenceSystem findCoordinateReferenceSystem(final 
String code) throws FactoryException {
+        if (crsFactory != null) {
+            return crsFactory.createCoordinateReferenceSystem(code);
+        }
+        return CRS.forCode(code);
+    }
+
+    /**
+     * Extracts coordinate operation details from the registry. If this {@code 
RegisterOperations}
+     * has not been restricted to a specific authority by a call to {@link 
#withAuthority(String)},
+     * then the given code must contain the authority.
+     *
+     * @param  code  operation identifier allocated by the authority.
+     * @return the operation for the given authority code.
+     * @throws NoSuchAuthorityCodeException if the specified {@code code} was 
not found.
+     * @throws FactoryException if the search failed for some other reason.
+     */
+    @Override
+    public CoordinateOperation findCoordinateOperation(String code) throws 
FactoryException {
+        if (crsFactory instanceof CoordinateOperationAuthorityFactory) {
+            ((CoordinateOperationAuthorityFactory) 
crsFactory).createCoordinateOperation(code);
+        }
+        return AuthorityFactories.ALL.createCoordinateOperation(code);
+    }
+
+    /**
+     * Finds or infers any coordinate operations for which the given 
<abbr>CRS</abbr>s are the source and target,
+     * in that order. This method searches for operation paths defined in the 
registry.
+     * If none are found, this method tries to infer a path itself.
+     *
+     * @param  source  the source <abbr>CRS</abbr>.
+     * @param  target  the target <abbr>CRS</abbr>.
+     * @return coordinate operations found or inferred between the given pair 
<abbr>CRS</abbr>s. May be an empty set.
+     * @throws FactoryException if an error occurred while searching for 
coordinate operations.
+     */
+    @Override
+    public Set<CoordinateOperation> 
findCoordinateOperations(CoordinateReferenceSystem source, 
CoordinateReferenceSystem target)
+            throws FactoryException
+    {
+        final List<CoordinateOperation> operations = 
CRS.findOperations(source, target, areaOfInterest);
+        return new AbstractSet<>() {    // Assuming that the list does not 
contain duplicated elements.
+            @Override public Iterator<CoordinateOperation> iterator() {return 
operations.iterator();}
+            @Override public boolean isEmpty() {return operations.isEmpty();}
+            @Override public int size() {return operations.size();}
+        };
+    }
+
+    /**
+     * Determines whether two <abbr>CRS</abbr>s are members of one ensemble.
+     * If this method returns {@code true}, then for low accuracy purposes 
coordinate sets referenced
+     * to these <abbr>CRS</abbr>s may be merged without coordinate 
transformation.
+     * The attribute {@link DatumEnsemble#getEnsembleAccuracy()} gives some 
indication
+     * of the inaccuracy introduced through such merger.
+     *
+     * @param  source  the source <abbr>CRS</abbr>.
+     * @param  target  the target <abbr>CRS</abbr>.
+     * @return whether the two <abbr>CRS</abbr>s are members of one ensemble.
+     * @throws FactoryException if an error occurred while searching for 
ensemble information in the registry.
+     */
+    @Override
+    public boolean areMembersOfSameEnsemble(CoordinateReferenceSystem source, 
CoordinateReferenceSystem target)
+            throws FactoryException
+    {
+        return (source instanceof SingleCRS) && (target instanceof SingleCRS)
+                && Utilities.equalsIgnoreMetadata(
+                        ((SingleCRS) source).getDatumEnsemble(),
+                        ((SingleCRS) target).getDatumEnsemble());
+    }
+
+    /**
+     * Returns a factory used for building components of <abbr>CRS</abbr> or 
coordinate operations.
+     * The factories returned by this method provide accesses to the low-level 
services used by this
+     * {@code RegisterOperations} instance for implementing its high-level 
services.
+     *
+     * @param  <T>   compile-time value of the {@code type} argument.
+     * @param  type  the desired type of factory.
+     * @return factory of the specified type.
+     * @throws NullPointerException if the specified type is null.
+     * @throws IllegalArgumentException if the specified type is not one of 
the above-cited values.
+     */
+    @Override
+    public <T extends Factory> Optional<T> getFactory(final Class<T> type) {
+        final Factory factory;
+        final Boolean b = FACTORY_TYPES.get(type);
+        if (b != null) {
+            if (b) {
+                final MultiAuthoritiesFactory mf = AuthorityFactories.ALL;
+                if (authority == null) {
+                    factory = mf;
+                } else try {
+                    factory = 
mf.getAuthorityFactory(type.asSubclass(AuthorityFactory.class), authority, 
version);
+                } catch (NoSuchAuthorityFactoryException e) {
+                    Logging.recoverableException(AuthorityFactories.LOGGER, 
MultiRegisterOperations.class, "getFactory", e);
+                    return Optional.empty();
+                }
+            } else {
+                factory = GeodeticObjectFactory.provider();
+            }
+        } else if (type == CoordinateOperationFactory.class) {
+            factory = DefaultCoordinateOperationFactory.provider();
+        } else if (type == MathTransformFactory.class) {
+            factory = DefaultMathTransformFactory.provider();
+        } else {
+            throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, 
"type", type));
+        }
+        return Optional.of(type.cast(factory));
+    }
+}
diff --git 
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
index 3582606d22..b3a3d07594 100644
--- 
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
@@ -46,6 +46,7 @@ import org.opengis.metadata.extent.Extent;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.util.FactoryException;
 import org.opengis.util.InternationalString;
+import org.opengis.util.NoSuchIdentifierException;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArgumentChecks;
@@ -61,6 +62,7 @@ import org.apache.sis.referencing.privy.LazySet;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.BackingStoreException;
@@ -289,8 +291,10 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
 
     /**
      * Returns the database or specification that defines the codes recognized 
by this factory.
-     * The default implementation returns {@code null} since {@code 
MultiAuthoritiesFactory} is not
-     * about a particular authority.
+     * The default implementation returns {@code null} because {@code 
MultiAuthoritiesFactory}
+     * is not about a particular authority.
+     *
+     * @return the organization responsible for definitions in the registry, 
or {@code null} if none or many.
      */
     @Override
     public Citation getAuthority() {
@@ -1430,12 +1434,24 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
      *   
<li><code>http://www.opengis.net/def/<b>method</b>/</code><var>authority</var>{@code
 /}<var>version</var>{@code /}<var>code</var></li>
      * </ul>
      *
+     * If the given code is not found in the geodetic registry, then this 
method searches also among
+     * the {@linkplain DefaultMathTransformFactory#getOperationMethod(String) 
build-in methods}.
+     *
      * @return the operation method for the given code.
      * @throws FactoryException if the object creation failed.
      */
     @Override
     public OperationMethod createOperationMethod(final String code) throws 
FactoryException {
-        return create(AuthorityFactoryProxy.METHOD, code);
+        try {
+            return create(AuthorityFactoryProxy.METHOD, code);
+        } catch (NoSuchAuthorityCodeException e) {
+            try {
+                return 
DefaultMathTransformFactory.provider().getOperationMethod(code);
+            } catch (NoSuchIdentifierException s) {
+                e.addSuppressed(s);
+                throw e;
+            }
+        }
     }
 
     /**
diff --git 
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
index 33c46f35e5..6bbb7f6814 100644
--- 
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
@@ -63,6 +63,7 @@ import 
org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException;
 import org.apache.sis.referencing.privy.CoordinateOperations;
 import org.apache.sis.referencing.privy.EllipsoidalHeightCombiner;
 import org.apache.sis.referencing.privy.PositionalAccuracyConstant;
+import org.apache.sis.referencing.privy.ReferencingFactoryContainer;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.referencing.internal.DeferredCoordinateOperation;
 import org.apache.sis.referencing.internal.Resources;
@@ -1337,7 +1338,9 @@ class CoordinateOperationRegistry {
                 if (descriptor != null) {
                     final Identifier name = descriptor.getName();
                     if (name != null) {
-                        method = factory.getOperationMethod(name.getCode());
+                        final MathTransformFactory mtFactory = 
factorySIS.getMathTransformFactory();
+                        var c = new ReferencingFactoryContainer(null, null, 
null, null, factory, mtFactory);
+                        method = c.findOperationMethod(name.getCode());
                     }
                     if (method == null) {
                         method = factory.createOperationMethod(properties, 
descriptor);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
index 7f72c76705..80e25bccd7 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
@@ -273,8 +273,10 @@ public class DefaultCoordinateOperationFactory extends 
AbstractFactory implement
      * @throws FactoryException if the requested operation method cannot be 
fetched.
      *
      * @see DefaultMathTransformFactory#getOperationMethod(String)
+     *
+     * @deprecated Use {@link DefaultMathTransformFactory} instead.
      */
-    @Override
+    @Deprecated(since="1.5", forRemoval=true)
     public OperationMethod getOperationMethod(String name) throws 
FactoryException {
         return new ReferencingFactoryContainer(null, null, null, null, null, 
mtFactory).findOperationMethod(name);
     }
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java
index d96503c7c5..762cad6ece 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java
@@ -22,13 +22,16 @@ import javax.measure.quantity.Angle;
 import javax.measure.quantity.Length;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.cs.CSFactory;
 import org.opengis.referencing.cs.CartesianCS;
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.crs.CRSFactory;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.datum.DatumFactory;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.datum.GeodeticDatum;
@@ -36,8 +39,9 @@ import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.Conversion;
 import org.opengis.parameter.ParameterValueGroup;
 import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.MultiRegisterOperations;
 import org.apache.sis.referencing.operation.DefaultConversion;
-import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.measure.Units;
 
@@ -63,10 +67,7 @@ public final class GeodeticObjectFactoryTest extends 
ObjectFactoryTest {
      * Creates a new test suite using the singleton factory instance.
      */
     public GeodeticObjectFactoryTest() {
-        super(GeodeticObjectFactory.provider(),
-              GeodeticObjectFactory.provider(),
-              GeodeticObjectFactory.provider(),
-              DefaultCoordinateOperationFactory.provider());
+        super(MultiRegisterOperations.provider());
     }
 
     /**
@@ -78,6 +79,7 @@ public final class GeodeticObjectFactoryTest extends 
ObjectFactoryTest {
      */
     @Test
     public void testCreateFromWKT() throws FactoryException {
+        final CRSFactory crsFactory = 
factories.getFactory(CRSFactory.class).orElseThrow();
         final GeodeticCRS crs = (GeodeticCRS) crsFactory.createFromWKT(
                 "GEOGCS[“WGS 84”,\n" +
                 "  DATUM[“World Geodetic System 1984”,\n" +
@@ -97,6 +99,7 @@ public final class GeodeticObjectFactoryTest extends 
ObjectFactoryTest {
      */
     @Test
     public void testInvalidParameterInWKT() throws FactoryException {
+        final CRSFactory crsFactory = 
factories.getFactory(CRSFactory.class).orElseThrow();
         var e = assertThrows(InvalidGeodeticParameterException.class,
                 () -> crsFactory.createFromWKT(
                 "PROJCRS[“Custom”,\n" +
@@ -134,6 +137,9 @@ public final class GeodeticObjectFactoryTest extends 
ObjectFactoryTest {
      */
     @Test
     public void testStepByStepCreation() throws FactoryException {
+        final CRSFactory   crsFactory   = 
factories.getFactory(CRSFactory.class).orElseThrow();
+        final CSFactory    csFactory    = 
factories.getFactory(CSFactory.class).orElseThrow();
+        final DatumFactory datumFactory = 
factories.getFactory(DatumFactory.class).orElseThrow();
         /*
          * List of all objects to be created in this test.
          */
@@ -189,7 +195,7 @@ public final class GeodeticObjectFactoryTest extends 
ObjectFactoryTest {
         /*
          * Defining conversion
          */
-        method = copFactory.getOperationMethod("Transverse_Mercator");
+        method = 
DefaultMathTransformFactory.provider().getOperationMethod("Transverse_Mercator");
         parameters = method.getParameters().createValue();
         parameters.parameter("semi_major")        
.setValue(ellipsoid.getSemiMajorAxis());
         parameters.parameter("semi_minor")        
.setValue(ellipsoid.getSemiMinorAxis());
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/geoapi/AuthorityFactoryTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/geoapi/AuthorityFactoryTest.java
index 4942e3c371..9912eeb342 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/geoapi/AuthorityFactoryTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/geoapi/AuthorityFactoryTest.java
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.geoapi;
 
 import org.opengis.util.FactoryException;
 import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.MultiRegisterOperations;
 
 // Test dependencies
 import org.junit.jupiter.api.Disabled;
@@ -42,7 +43,7 @@ public final class AuthorityFactoryTest extends 
org.opengis.test.referencing.Aut
      * @throws FactoryException if no factory can be returned for the given 
authority.
      */
     public AuthorityFactoryTest() throws FactoryException {
-        super(CRS.getAuthorityFactory(null), null, null);
+        super(MultiRegisterOperations.provider());
     }
 
     /**
diff --git a/geoapi/snapshot b/geoapi/snapshot
index a07e965123..a0ae30dd67 160000
--- a/geoapi/snapshot
+++ b/geoapi/snapshot
@@ -1 +1 @@
-Subproject commit a07e965123706be9092d0dc615592c23c8401450
+Subproject commit a0ae30dd67b67a718b74bd8a90140ea0a559e8b6


Reply via email to