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 3a6d0acf80 Deprecate 
`DefaultMathTransformFactory.createParameterizedTransform(ParameterValueGroup, 
Context)`. This is replaced by a builder.
3a6d0acf80 is described below

commit 3a6d0acf805e39a21eab1070eed03edf5cd14944
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sun Jun 30 05:00:29 2024 +0200

    Deprecate 
`DefaultMathTransformFactory.createParameterizedTransform(ParameterValueGroup, 
Context)`.
    This is replaced by a builder.
---
 .../referencing/factory/sql/EPSGDataAccess.java    |   3 +-
 .../operation/AbstractSingleOperation.java         |   4 +-
 .../operation/CoordinateOperationFinder.java       |  16 +-
 .../operation/CoordinateOperationRegistry.java     |  41 +-
 .../referencing/operation/DefaultConversion.java   |  42 +-
 .../DefaultCoordinateOperationFactory.java         |   3 +-
 .../operation/MathTransformContext.java            |   5 -
 .../operation/projection/package-info.java         |   5 +-
 .../transform/DefaultMathTransformFactory.java     | 512 ++++++++++++++-------
 .../referencing/privy/ReferencingUtilities.java    |  68 +--
 .../operation/provider/GeographicOffsetsTest.java  |  13 +-
 .../transform/DefaultMathTransformFactoryTest.java |  18 +-
 .../transform/MathTransformFactoryBase.java        |   8 +
 .../transform/MathTransformFactoryMock.java        |  13 +
 geoapi/snapshot                                    |   2 +-
 15 files changed, 451 insertions(+), 302 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 497646caba..f4694584cd 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -2950,7 +2950,8 @@ next:                   while (r.next()) {
                          */
                         opProperties = new HashMap<>(opProperties);            
 // Because this class uses a shared map.
                         final MathTransformFactory mtFactory = owner.mtFactory;
-                        final MathTransform mt = 
ReferencingUtilities.createBaseToDerived(mtFactory, sourceCRS, parameters, 
targetCRS);
+                        final MathTransform mt = ReferencingUtilities.builder(
+                                mtFactory, parameters, sourceCRS, 
targetCRS).create();
                         /*
                          * Give a hint to the factory about the type of the 
coordinate operation. ISO 19111 defines
                          * Conversion and Transformation, but SIS also have 
more specific sub-types.  We begin with
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractSingleOperation.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractSingleOperation.java
index a721f0d225..de3287b2e1 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractSingleOperation.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractSingleOperation.java
@@ -468,8 +468,8 @@ class AbstractSingleOperation extends 
AbstractCoordinateOperation implements Sin
         final CoordinateReferenceSystem sourceCRS = super.getSourceCRS();
         final CoordinateReferenceSystem targetCRS = super.getTargetCRS();
         if (transform == null && sourceCRS != null && targetCRS != null && 
parameters != null) try {
-            transform = 
ReferencingUtilities.createBaseToDerived(DefaultMathTransformFactory.provider(),
-                                                                 sourceCRS, 
parameters, targetCRS);
+            transform = 
ReferencingUtilities.builder(DefaultMathTransformFactory.provider(),
+                                                     parameters, sourceCRS, 
targetCRS).create();
         } catch (FactoryException e) {
             Context.warningOccured(Context.current(), 
AbstractSingleOperation.class, "afterUnmarshal", e, true);
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
index 39a7d78e28..1c2313349b 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
@@ -513,6 +513,8 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
                                                             final GeodeticCRS 
targetCRS)
             throws FactoryException
     {
+        final CoordinateSystem sourceCS = sourceCRS.getCoordinateSystem();
+        final CoordinateSystem targetCS = targetCRS.getCoordinateSystem();
         final GeodeticDatum sourceDatum = sourceCRS.getDatum();
         final GeodeticDatum targetDatum = targetCRS.getDatum();
         Matrix datumShift = null;
@@ -525,8 +527,8 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
          * prime meridians are not the same, then the target meridian must be 
Greenwich.
          */
         final DefaultMathTransformFactory.Context context = new 
MathTransformContext(sourceDatum, targetDatum);
-        context.setSource(sourceCRS);
-        context.setTarget(targetCRS);
+        context.setSourceAxes(sourceCS, sourceDatum.getEllipsoid());
+        context.setTargetAxes(targetCS, targetDatum.getEllipsoid());
         /*
          * If both CRS use the same datum and the same prime meridian, then 
the coordinate operation is only axis
          * swapping, unit conversion or change of coordinate system type 
(Ellipsoidal ↔ Cartesian ↔ Spherical).
@@ -536,8 +538,6 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
          */
         Identifier identifier;
         boolean isGeographicToGeocentric = false;
-        final CoordinateSystem sourceCS = context.getSourceCS();
-        final CoordinateSystem targetCS = context.getTargetCS();
         if (equalsIgnoreMetadata(sourceDatum, targetDatum)) {
             final boolean isGeocentricToGeographic;
             isGeographicToGeocentric = (sourceCS instanceof EllipsoidalCS && 
targetCS instanceof CartesianCS);
@@ -612,8 +612,8 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
                 final CoordinateSystem normalized = 
CommonCRS.WGS84.geocentric().getCoordinateSystem();
                 before = mtFactory.createCoordinateSystemChange(sourceCS, 
normalized, sourceDatum.getEllipsoid());
                 after  = mtFactory.createCoordinateSystemChange(normalized, 
targetCS, targetDatum.getEllipsoid());
-                context.setSource(normalized);
-                context.setTarget(normalized);
+                context.setSourceAxes(normalized, null);
+                context.setTargetAxes(normalized, null);
                 /*
                  * The name of the `parameters` group determines the 
`OperationMethod` later in this method.
                  * We cannot leave that name to "Affine" if `before` or 
`after` transforms are not identity.
@@ -655,7 +655,7 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
                  */
                 before = mtFactory.createCoordinateSystemChange(sourceCS, 
targetCS,
                         (sourceCS instanceof EllipsoidalCS ? sourceDatum : 
targetDatum).getEllipsoid());
-                context.setSource(targetCS);
+                context.setSourceAxes(targetCS, null);
                 method = mtFactory.getLastMethodUsed();
             }
         }
@@ -675,7 +675,7 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
          */
         MathTransform transform = 
mtFactory.createParameterizedTransform(parameters, context);
         if (method == null) {
-            method = context.getMethodUsed();
+            method = context.getMethod().orElse(null);
         }
         if (before != null) {
             parameters = null;      // Providing parameters would be 
misleading because they apply to only a step of the operation.
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 6bbb7f6814..c4aa7c3866 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
@@ -53,7 +53,6 @@ import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
-import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.factory.UnavailableFactoryException;
@@ -1023,13 +1022,11 @@ class CoordinateOperationRegistry {
             CoordinateReferenceSystem crs;
             if (Utilities.equalsApproximately(sourceCRS, crs = 
operation.getSourceCRS())) sourceCRS = crs;
             if (Utilities.equalsApproximately(targetCRS, crs = 
operation.getTargetCRS())) targetCRS = crs;
-            final MathTransformFactory mtFactory = 
factorySIS.getMathTransformFactory();
-            if (mtFactory instanceof DefaultMathTransformFactory) {
-                MathTransform mt = ((DefaultMathTransformFactory) 
mtFactory).createParameterizedTransform(
-                        parameters, 
ReferencingUtilities.createTransformContext(sourceCRS, targetCRS));
-                return 
factorySIS.createSingleOperation(IdentifiedObjects.getProperties(operation),
-                        sourceCRS, targetCRS, null, operation.getMethod(), mt);
-            }
+            var builder = ReferencingUtilities.builder(
+                    factorySIS.getMathTransformFactory(), parameters, 
sourceCRS, targetCRS);
+            final MathTransform mt = builder.create();      // Must be before 
`operation.getMethod()`.
+            return 
factorySIS.createSingleOperation(IdentifiedObjects.getProperties(operation),
+                    sourceCRS, targetCRS, null, operation.getMethod(), mt);
         } else {
             // Should never happen because parameters are mandatory, but let 
be safe.
             log(resources().getLogRecord(Level.WARNING, 
Resources.Keys.MissingParameterValues_1,
@@ -1141,22 +1138,20 @@ class CoordinateOperationRegistry {
             if (matrix == null) {
                 if (op instanceof SingleOperation) {
                     final MathTransformFactory mtFactory = 
factorySIS.getMathTransformFactory();
-                    if (mtFactory instanceof DefaultMathTransformFactory) {
-                        if (forward) sourceCRS = toGeodetic3D(sourceCRS, 
source3D);
-                        else         targetCRS = toGeodetic3D(targetCRS, 
target3D);
-                        final DefaultMathTransformFactory.Context context;
-                        final MathTransform mt;
-                        try {
-                            context = 
ReferencingUtilities.createTransformContext(sourceCRS, targetCRS);
-                            mt = ((DefaultMathTransformFactory) 
mtFactory).createParameterizedTransform(
-                                    ((SingleOperation) 
op).getParameterValues(), context);
-                        } catch (InvalidGeodeticParameterException e) {
-                            log(null, e);
-                            break;
-                        }
-                        operations.set(recreate(op, sourceCRS, targetCRS, mt, 
context.getMethodUsed()));
-                        return true;
+                    if (forward) sourceCRS = toGeodetic3D(sourceCRS, source3D);
+                    else         targetCRS = toGeodetic3D(targetCRS, target3D);
+                    final MathTransform.Builder builder;
+                    final MathTransform mt;
+                    try {
+                        final var parameters = ((SingleOperation) 
op).getParameterValues();
+                        builder = ReferencingUtilities.builder(mtFactory, 
parameters, sourceCRS, targetCRS);
+                        mt = builder.create();
+                    } catch (InvalidGeodeticParameterException e) {
+                        log(null, e);
+                        break;
                     }
+                    operations.set(recreate(op, sourceCRS, targetCRS, mt, 
builder.getMethod().orElse(null)));
+                    return true;
                 }
                 break;
             }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
index b3d96d3e2a..f7358bb8d5 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
@@ -237,41 +237,31 @@ public class DefaultConversion extends 
AbstractSingleOperation implements Conver
             if (parameters == null) {
                 throw new 
IllegalArgumentException(Resources.format(Resources.Keys.UnspecifiedParameterValues));
             }
-            if (factory instanceof DefaultMathTransformFactory) {
-                /*
-                 * Apache SIS specific API (not yet defined in GeoAPI, but 
could be proposed).
-                 * Note that setTarget(…) intentionally uses only the 
CoordinateSystem instead of the full
-                 * CoordinateReferenceSystem because the targetCRS is 
typically under construction when this
-                 * method in invoked, and attempts to use it can cause 
NullPointerException.
-                 */
-                final DefaultMathTransformFactory.Context context;
-                if (target instanceof DerivedCRS) {
-                    context = 
ReferencingUtilities.createTransformContext(source, null);
-                    context.setTarget(target.getCoordinateSystem());    // 
Using `target` would be unsafe here.
-                } else {
-                    context = 
ReferencingUtilities.createTransformContext(source, target);
-                }
-                transform = ((DefaultMathTransformFactory) 
factory).createParameterizedTransform(parameters, context);
+            /*
+             * Note that setTargetAxes(…) intentionally uses only the 
CoordinateSystem instead of the full
+             * CoordinateReferenceSystem because the targetCRS is typically 
under construction when this
+             * method in invoked, and attempts to use it can cause 
NullPointerException.
+             */
+            final boolean isDerived = (target instanceof DerivedCRS);
+            var builder = ReferencingUtilities.builder(factory, parameters, 
source, isDerived ? null : target);
+            if (isDerived) {
+                builder.setTargetAxes(target.getCoordinateSystem(), null);
+            }
+            transform = builder.create();
+            if (builder instanceof DefaultMathTransformFactory.Context) {
+                final var context = (DefaultMathTransformFactory.Context) 
builder;
                 setParameterValues(context.getCompletedParameters(), 
context.getContextualParameters());
-            } else {
-                /*
-                 * Fallback for non-SIS implementation. Equivalent to the 
above code, except that we can
-                 * not get the parameters completed with semi-major and 
semi-minor axis lengths. Most of
-                 * the code should work anyway.
-                 */
-                transform = factory.createBaseToDerived(source, parameters, 
target.getCoordinateSystem());
             }
         } else {
             /*
              * If the user specified explicitly a MathTransform, we may still 
need to swap or scale axes.
              * If this conversion is a defining conversion (which is usually 
the case when creating a new
-             * ProjectedCRS), then DefaultMathTransformFactory has a 
specialized createBaseToDerived(…)
-             * method for this job.
+             * ProjectedCRS), then MathTransformFactory has a specialized 
builder(…) method for this job.
              */
             if (sourceCRS == null && targetCRS == null && factory instanceof 
DefaultMathTransformFactory) {
                 final var context = new DefaultMathTransformFactory.Context();
-                context.setSource(source.getCoordinateSystem());
-                context.setTarget(target.getCoordinateSystem());    // See 
comment on the other setTarget(…) call.
+                context.setSourceAxes(source.getCoordinateSystem(), null);
+                context.setTargetAxes(target.getCoordinateSystem(), null);    
// See comment on the other setTargetAxes(…) call.
                 transform = ((DefaultMathTransformFactory) 
factory).swapAndScaleAxes(transform, context);
             } else {
                 /*
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 80e25bccd7..52c794c1cc 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
@@ -506,7 +506,8 @@ next:   for (int i=components.size(); --i >= 0;) {
             if (parameters == null) {
                 throw new 
NullPointerException(Errors.format(Errors.Keys.NullArgument_1, "transform"));
             }
-            transform = 
ReferencingUtilities.createBaseToDerived(getMathTransformFactory(), sourceCRS, 
parameters, targetCRS);
+            transform = ReferencingUtilities.builder(
+                    getMathTransformFactory(), parameters, sourceCRS, 
targetCRS).create();
         }
         /*
          * The "operationType" property is currently undocumented. The intent 
is to help this factory method in
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java
index 90b94e6f6d..350c25ed91 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java
@@ -43,11 +43,6 @@ import org.apache.sis.measure.Units;
  * @author  Martin Desruisseaux (Geomatys)
  */
 final class MathTransformContext extends Context {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 8765209303733056283L;
-
     /**
      * The longitude of the source and target prime meridian, in number of 
degrees East of Greenwich.
      */
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/package-info.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/package-info.java
index 69564d8421..7b28b53b7b 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/package-info.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/package-info.java
@@ -99,9 +99,8 @@
  * The real axis flip is performed outside this projection package, upon
  * {@linkplain org.opengis.referencing.cs.CoordinateSystemAxis coordinate 
system axis} inspection,
  * as a concatenation of the North oriented cartographic projection with an 
affine transform.
- * Such axis analysis and transforms concatenation can be performed 
automatically by the
- * {@link 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory#createBaseToDerived
- * createBaseToDerived(…)} method defined in the {@code MathTransformFactory} 
interface.
+ * Such axis analysis and transforms concatenation can be performed 
automatically by
+ * {@link 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory.Context}.
  * The same rule applies to the <cite>Krovak</cite> projection as well (at the 
opposite of what ESRI does).
  *
  * <p>In order to reduce the risk of confusion, this package never defines 
south oriented map projection.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
index 84e54a6232..d11a29a3e5 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.ServiceLoader;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.OptionalInt;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -29,7 +30,6 @@ import java.util.concurrent.atomic.AtomicReference;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.lang.reflect.Constructor;
-import java.io.Serializable;
 import javax.measure.Unit;
 import javax.measure.IncommensurableException;
 import javax.measure.quantity.Length;
@@ -45,6 +45,7 @@ import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.CartesianCS;
+import org.opengis.referencing.cs.SphericalCS;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
@@ -85,6 +86,9 @@ import org.apache.sis.util.iso.AbstractFactory;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.util.UnimplementedServiceException;
+
 
 /**
  * Low level factory for creating {@linkplain AbstractMathTransform math 
transforms}.
@@ -476,6 +480,39 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         return method;
     }
 
+    /**
+     * Returns a builder for a parameterized math transform using the 
specified operation method.
+     * The {@code method} argument should be the name or identifier of an 
{@link OperationMethod}
+     * instance returned by <code>{@link #getAvailableMethods(Class) 
getAvailableMethods}(null)</code>.
+     *
+     * <p>The returned builder allows to specify not only the operation 
parameter values,
+     * but also some contextual information such as the source and target axes.
+     * The builder uses these information for:</p>
+     *
+     * <ol>
+     *   <li>Inferring the {@code "semi_major"}, {@code "semi_minor"}, {@code 
"src_semi_major"},
+     *       {@code "src_semi_minor"}, {@code "tgt_semi_major"} or {@code 
"tgt_semi_minor"} parameter values
+     *       from the {@link Ellipsoid} associated to the source or target 
CRS, if these parameters are
+     *       not explicitly given and if they are relevant for the coordinate 
operation method.</li>
+     *   <li>{@linkplain #createConcatenatedTransform Concatenating} the 
parameterized transform
+     *       with any other transforms required for performing units changes 
and coordinates swapping.</li>
+     * </ol>
+     *
+     * The complete group of parameters, including {@code "semi_major"}, 
{@code "semi_minor"} or other
+     * calculated values, can be obtained by a call to {@link 
Context#getCompletedParameters()}.
+     *
+     * @param  method  the case insensitive name or identifier of the desired 
coordinate operation method.
+     * @return a builder for a meth transform implementing the formulas 
identified by the given method.
+     * @throws NoSuchIdentifierException if there is no supported method for 
the given name or identifier.
+     *
+     * @see #getAvailableMethods(Class)
+     * @since 1.5
+     */
+    @Override
+    public MathTransform.Builder builder(final String method) throws 
NoSuchIdentifierException {
+        return new Context(this, getOperationMethod(method));
+    }
+
     /**
      * Returns the default parameter values for a math transform using the 
given operation method.
      * The {@code method} argument is the name of any {@code OperationMethod} 
instance returned by
@@ -500,36 +537,16 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
     }
 
     /**
-     * Creates a transform from a group of parameters.
-     * The set of expected parameters varies for each operation.
-     *
-     * @param  parameters  the parameter values. The {@linkplain 
ParameterDescriptorGroup#getName() parameter group name}
-     *         shall be the name of the desired {@linkplain 
DefaultOperationMethod operation method}.
-     * @return the transform created from the given parameters.
-     * @throws NoSuchIdentifierException if there is no method for the given 
parameter group name.
-     * @throws FactoryException if the object creation failed. This exception 
is thrown
-     *         if some required parameter has not been supplied, or has 
illegal value.
-     *
-     * @deprecated Replaced by {@link 
#createParameterizedTransform(ParameterValueGroup, Context)}
-     *             where the {@code Context} argument can be null.
-     */
-    @Override
-    @Deprecated(since="0.7")
-    public MathTransform createParameterizedTransform(final 
ParameterValueGroup parameters)
-            throws NoSuchIdentifierException, FactoryException
-    {
-        return createParameterizedTransform(parameters, null);
-    }
-
-    /**
-     * Source and target coordinate systems for which a new parameterized 
transform is going to be used.
+     * Builder of a parameterized math transform identified by a name or code.
+     * A builder can optionally contain the source and target coordinate 
systems
+     * for which a new parameterized transform is going to be used.
      * {@link DefaultMathTransformFactory} uses this information for:
      *
      * <ul>
      *   <li>Completing some parameters if they were not provided. In 
particular, the {@linkplain #getSourceEllipsoid()
      *       source ellipsoid} can be used for providing values for the {@code 
"semi_major"} and {@code "semi_minor"}
      *       parameters in map projections.</li>
-     *   <li>{@linkplain CoordinateSystems#swapAndScaleAxes Swapping and 
scaling axes} if the source or the target
+     *   <li>{@linkplain #swapAndScaleAxes Swapping and scaling axes} if the 
source or the target
      *       coordinate systems are not {@linkplain AxesConvention#NORMALIZED 
normalized}.</li>
      * </ul>
      *
@@ -538,25 +555,19 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
      * or anything else related to datum. Datum changes have dedicated {@link 
OperationMethod},
      * for example <q>Longitude rotation</q> (EPSG:9601) for changing the 
prime meridian.
      *
-     * <h2>Scope</h2>
-     * Instances of this class should be short-lived
-     * (they exist only the time needed for creating a {@link MathTransform})
-     * and should not be shared (because they provide no immutability 
guarantees).
-     * This class is not thread-safe.
+     * <p>Each instance is created by {@link #builder(String)} and should be 
used only once.
+     * This class is <em>not</em> thread-safe.</p>
      *
      * @author  Martin Desruisseaux (Geomatys)
      * @version 1.5
      * @since   0.7
+     *
+     * @see #builder(String)
      */
-    @SuppressWarnings("serial")         // All field values are usually 
serializable instances.
-    public static class Context implements MathTransformProvider.Context, 
Serializable {
-        /**
-         * For cross-version compatibility.
-         */
-        private static final long serialVersionUID = -239563539875674709L;
-
+    public static class Context implements MathTransform.Builder, 
MathTransformProvider.Context {
         /**
          * The factory to use if the provider needs to create other math 
transforms as operation steps.
+         * @todo Replace by non-static inner class after deprecated methods 
have been removed.
          *
          * @see #getFactory()
          */
@@ -569,21 +580,23 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
 
         /**
          * The ellipsoid of the source or target ellipsoidal coordinate 
system, or {@code null} if it does not apply.
-         * Valid only if {@link #sourceCS} or {@link #targetCS} is an instance 
of {@link EllipsoidalCS}.
          */
         private Ellipsoid sourceEllipsoid, targetEllipsoid;
 
         /**
          * The provider that created the parameterized {@link MathTransform} 
instance, or {@code null}
-         * if this information does not apply. This field is used for 
transferring information between
-         * {@code createParameterizedTransform(…)} and {@code 
swapAndScaleAxes(…)}.
+         * if this information does not apply. This is initially set to the 
operation method specified
+         * in the call to {@link #builder(String)}, but may be modified by 
{@link #create()}.
+         *
+         * @see #getMethod()
          */
         private OperationMethod provider;
 
         /**
          * The parameters actually used.
+         * @todo Make final after the deprecated methods have been removed.
          *
-         * @see #getCompletedParameters()
+         * @see #parameters()
          */
         private ParameterValueGroup parameters;
 
@@ -594,17 +607,47 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
          */
         private final Map<String,Boolean> contextualParameters;
 
+        /**
+         * Whether the user-specified parameters have been completed with the 
contextual parameters.
+         * This set to {@code true} the first time that {@link 
#getCompletedParameters()} is invoked.
+         * After this flag became {@code true}, this {@code Context} should 
not be modified anymore.
+         *
+         * @see #completeParameters()
+         */
+        private boolean completedParameters;
+
+        /**
+         * The warning that occurred during parameters completion, or {@code 
null} if none.
+         * This warning is not necessarily fatal, but will be appended to the 
suppressed exceptions
+         * of {@link FactoryException} if the {@link MathTransform} creation 
nevertheless fail.
+         */
+        private RuntimeException warning;
+
         /**
          * Creates a new context with all properties initialized to {@code 
null}.
+         *
+         * @deprecated Use {@link #builder(String)} instead.
          */
+        @Deprecated(since="1.5", forRemoval=true)
         public Context() {
             contextualParameters = new HashMap<>();
         }
 
+        /**
+         * Creates a new context with all properties initialized to {@code 
null}.
+         *
+         * @param  method  a method known to the enclosing factory.
+         */
+        Context(final DefaultMathTransformFactory factory, final 
OperationMethod method) {
+            this.factory = factory;
+            provider = method;
+            parameters = method.getParameters().createValue();
+            contextualParameters = new HashMap<>();
+        }
+
         /**
          * Returns the factory to use if the provider needs to create other 
math transforms as operation steps.
-         * This is the factory on which {@link 
#createParameterizedTransform(ParameterValueGroup, Context)} is
-         * invoked, or the {@linkplain #provider() default factory} otherwise.
+         * This is the factory on which {@link #builder(String)} has been 
invoked.
          *
          * @since 1.5
          */
@@ -613,15 +656,39 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
             return (factory != null) ? factory : provider();
         }
 
+        /**
+         * Gives hints about axis lengths and their orientations in input 
coordinates.
+         * The {@code ellipsoid} argument is often provided together with an 
{@link EllipsoidalCS}, but not only.
+         * For example, a two-dimensional {@link SphericalCS} may also require 
information about the ellipsoid.
+         *
+         * <p>Each call to this method replaces the values of the previous 
call.
+         * However, this method cannot be invoked anymore after {@link 
#getCompletedParameters()} has been invoked.</p>
+         *
+         * @param  cs  the coordinate system defining source axis order and 
units, or {@code null} if none.
+         * @param  ellipsoid  the ellipsoid providing source semi-axis 
lengths, or {@code null} if none.
+         * @throws IllegalStateException if {@link #getCompletedParameters()} 
has already been invoked.
+         *
+         * @since 1.5
+         */
+        @Override
+        public void setSourceAxes(CoordinateSystem cs, Ellipsoid ellipsoid) {
+            if (completedParameters) {
+                throw new 
IllegalStateException(Errors.format(Errors.Keys.AlreadyInitialized_1, 
"completedParameters"));
+            }
+            sourceCS = cs;
+            sourceEllipsoid = ellipsoid;
+        }
+
         /**
          * Sets the source coordinate system to the given value.
          * The source ellipsoid is unconditionally set to {@code null}.
          *
          * @param  cs  the coordinate system to set as the source (can be 
{@code null}).
+         * @deprecated Replaced by {@link #setSourceAxes(CoordinateSystem, 
Ellipsoid)} for simplifying the API.
          */
+        @Deprecated(since="1.5", forRemoval=true)
         public void setSource(final CoordinateSystem cs) {
-            sourceCS = cs;
-            sourceEllipsoid = null;
+            setSourceAxes(cs, null);
         }
 
         /**
@@ -636,15 +703,38 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
          * @param  crs  the coordinate system and ellipsoid to set as the 
source, or {@code null}.
          *
          * @since 1.3
+         * @deprecated Replaced by {@link #setSourceAxes(CoordinateSystem, 
Ellipsoid)} for simplifying the API.
          */
+        @Deprecated(since="1.5", forRemoval=true)
         public void setSource(final GeodeticCRS crs) {
             if (crs != null) {
-                sourceCS = crs.getCoordinateSystem();
-                sourceEllipsoid = crs.getDatum().getEllipsoid();
+                setSourceAxes(crs.getCoordinateSystem(), 
crs.getDatum().getEllipsoid());
             } else {
-                sourceCS = null;
-                sourceEllipsoid = null;
+                setSourceAxes(null, null);
+            }
+        }
+
+        /**
+         * Gives hints about axis lengths and their orientations in output 
coordinates.
+         * The {@code ellipsoid} argument is often provided together with an 
{@link EllipsoidalCS}, but not only.
+         * For example, a two-dimensional {@link SphericalCS} may also require 
information about the ellipsoid.
+         *
+         * <p>Each call to this method replaces the values of the previous 
call.
+         * However, this method cannot be invoked anymore after {@link 
#getCompletedParameters()} has been invoked.</p>
+         *
+         * @param  cs  the coordinate system defining target axis order and 
units, or {@code null} if none.
+         * @param  ellipsoid  the ellipsoid providing target semi-axis 
lengths, or {@code null} if none.
+         * @throws IllegalStateException if {@link #getCompletedParameters()} 
has already been invoked.
+         *
+         * @since 1.5
+         */
+        @Override
+        public void setTargetAxes(CoordinateSystem cs, Ellipsoid ellipsoid) {
+            if (completedParameters) {
+                throw new 
IllegalStateException(Errors.format(Errors.Keys.AlreadyInitialized_1, 
"completedParameters"));
             }
+            targetCS = cs;
+            targetEllipsoid = ellipsoid;
         }
 
         /**
@@ -652,10 +742,11 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
          * The target ellipsoid is unconditionally set to {@code null}.
          *
          * @param  cs  the coordinate system to set as the target (can be 
{@code null}).
+         * @deprecated Replaced by {@link #setTargetAxes(CoordinateSystem, 
Ellipsoid)} for simplifying the API.
          */
+        @Deprecated(since="1.5", forRemoval=true)
         public void setTarget(final CoordinateSystem cs) {
-            targetCS = cs;
-            targetEllipsoid = null;
+            setTargetAxes(cs, null);
         }
 
         /**
@@ -670,14 +761,14 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
          * @param  crs  the coordinate system and ellipsoid to set as the 
target, or {@code null}.
          *
          * @since 1.3
+         * @deprecated Replaced by {@link #setTargetAxes(CoordinateSystem, 
Ellipsoid)} for simplifying the API.
          */
+        @Deprecated(since="1.5", forRemoval=true)
         public void setTarget(final GeodeticCRS crs) {
             if (crs != null) {
-                targetCS = crs.getCoordinateSystem();
-                targetEllipsoid = crs.getDatum().getEllipsoid();
+                setTargetAxes(crs.getCoordinateSystem(), 
crs.getDatum().getEllipsoid());
             } else {
-                targetCS = null;
-                targetEllipsoid = null;
+                setTargetAxes(null, null);
             }
         }
 
@@ -691,10 +782,9 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         }
 
         /**
-         * Returns the ellipsoid of the source ellipsoidal coordinate system, 
or {@code null} if it does not apply.
-         * This information is valid only if {@link #getSourceCS()} returns an 
instance of {@link EllipsoidalCS}.
+         * Returns the ellipsoid used together with the source coordinate 
system, or {@code null} if none.
          *
-         * @return the ellipsoid of the source ellipsoidal coordinate system, 
or {@code null} if it does not apply.
+         * @return the ellipsoid used together with the source coordinate 
system, or {@code null} if none.
          */
         public Ellipsoid getSourceEllipsoid() {
             return sourceEllipsoid;
@@ -721,10 +811,9 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         }
 
         /**
-         * Returns the ellipsoid of the target ellipsoidal coordinate system, 
or {@code null} if it does not apply.
-         * This information is valid only if {@link #getTargetCS()} returns an 
instance of {@link EllipsoidalCS}.
+         * Returns the ellipsoid used together with the target coordinate 
system, or {@code null} if none.
          *
-         * @return the ellipsoid of the target ellipsoidal coordinate system, 
or {@code null} if it does not apply.
+         * @return the ellipsoid used together with the target coordinate 
system, or {@code null} if none.
          */
         public Ellipsoid getTargetEllipsoid() {
             return targetEllipsoid;
@@ -801,6 +890,21 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
             }
         }
 
+        /**
+         * Returns the operation method used for creating the math transform 
from the parameter values.
+         * This is initially the operation method specified in the call to 
{@link #builder(String)},
+         * but may change after the call to {@link #create()} if the method 
has been adjusted because
+         * of the parameter values.
+         *
+         * @return the operation method used for creating the math transform 
from the parameter values.
+         *
+         * @since 1.5
+         */
+        @Override
+        public Optional<OperationMethod> getMethod() {
+            return Optional.ofNullable(provider);
+        }
+
         /**
          * Returns the operation method used for the math transform creation.
          * This is the same information as {@link #getLastMethodUsed()} but 
more stable
@@ -810,10 +914,11 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
          * @throws IllegalStateException if {@link 
#createParameterizedTransform(ParameterValueGroup, Context)}
          *         has not yet been invoked.
          *
-         * @see #getLastMethodUsed()
+         * @deprecated Replaced by {@link #getMethod()}.
          *
          * @since 1.3
          */
+        @Deprecated(since="1.5", forRemoval=true)
         public OperationMethod getMethodUsed() {
             if (provider != null) {
                 return provider;
@@ -821,6 +926,24 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
             throw new 
IllegalStateException(Resources.format(Resources.Keys.UnspecifiedParameterValues));
         }
 
+        /**
+         * Returns the parameter values to modify for defining the transform 
to create.
+         * Those parameters are initialized to default values, which are 
{@linkplain #getMethod() method} depend.
+         * User-supplied values should be set directly in the returned 
instance with codes like
+         * 
<code>parameter(</code><var>name</var><code>).setValue(</code><var>value</var><code>)</code>.
+         *
+         * @return the parameter values to modify for defining the transform 
to create.
+         *
+         * @since 1.5
+         */
+        @Override
+        public ParameterValueGroup parameters() {
+            if (parameters == null) {
+                parameters = provider.getParameters().createValue();
+            }
+            return parameters;
+        }
+
         /**
          * Returns the names of parameters that have been inferred from the 
context.
          * The set of keys can contain any of {@code "dim"},
@@ -831,15 +954,15 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
          * The parameters named in that set are included in the parameters
          * returned by {@link #getCompletedParameters()}.
          *
-         * <h4>Associated boolean values</h4>
-         * The associated boolean in the map tells whether the named parameter 
value is really contextual.
-         * The boolean is {@code FALSE} if the user explicitly specified a 
value in the parameters given to
+         * <h4>Associated Boolean values</h4>
+         * The associated Boolean in the map tells whether the named parameter 
value is really contextual.
+         * The Boolean is {@code FALSE} if the user explicitly specified a 
value in the parameters given to
          * the {@link #createParameterizedTransform(ParameterValueGroup, 
Context)} method,
          * and that value is different than the value inferred from the 
context.
          * Such inconsistencies are also logged at {@link Level#WARNING}.
          * In all other cases
          * (no value specified by the user, or a value was specified but is 
consistent with the context),
-         * the associated boolean in the map is {@code TRUE}.
+         * the associated Boolean in the map is {@code TRUE}.
          *
          * <h4>Mutability</h4>
          * The returned map is modifiable for making easier for callers to 
amend the contextual information.
@@ -858,15 +981,26 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         /**
          * Returns the parameter values used for the math transform creation,
          * including the parameters completed by the factory.
-         * The parameters inferred from the context are listed by {@link 
#getContextualParameters()}.
+         * This is the union of {@link #parameters()} with {@link 
#getContextualParameters()}.
+         * The completed parameters may only have additional parameters 
compared to the user-supplied parameters.
+         * {@linkplain #parameters() Parameter} values that were explicitly 
set by the user are not overwritten.
+         *
+         * <p>After this method has been invoked, the {@link #setSourceAxes 
setSourceAxes(…)}
+         * and {@link #setTargetAxes setTargetAxes(…)} methods can no longer 
be invoked.</p>
          *
          * @return the parameter values used by the factory.
-         * @throws IllegalStateException if {@link 
#createParameterizedTransform(ParameterValueGroup, Context)}
-         *         has not yet been invoked.
          */
         @Override
         public ParameterValueGroup getCompletedParameters() {
             if (parameters != null) {
+                /*
+                 * If the user's parameters do not contain semi-major and 
semi-minor axis lengths, infer
+                 * them from the ellipsoid. We have to do that because those 
parameters are often omitted,
+                 * since the standard place where to provide this information 
is in the ellipsoid object.
+                 */
+                if (!completedParameters) {
+                    warning = completeParameters();
+                }
                 return parameters;
             }
             throw new 
IllegalStateException(Resources.format(Resources.Keys.UnspecifiedParameterValues));
@@ -885,6 +1019,8 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
          * @param  writable  {@code true} if this method should also check 
that the parameters group is editable.
          * @throws IllegalArgumentException if the copy cannot be performed 
because a parameter has
          *         a unrecognized name or an illegal value.
+         *
+         * @todo We should be able to remove this method after we removed the 
other deprecated methods.
          */
         private void ensureCompatibleParameters(final boolean writable) throws 
IllegalArgumentException {
             final ParameterDescriptorGroup expected = provider.getParameters();
@@ -1053,41 +1189,16 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         /**
          * Completes the parameter group with information about source and 
target ellipsoid axis lengths,
          * if available. This method writes semi-major and semi-minor 
parameter values only if they do not
-         * already exists in the given parameters.
+         * already exists in the current parameters.
          *
-         * <p>The given method and parameters are stored in the {@link 
#provider} and {@link #parameters}
-         * fields respectively. The actual stored values may differ from the 
values given to this method.
-         * The {@link #factory} field must be set before this method is 
invoked.</p>
-         *
-         * @param  method   description of the transform to be created, or 
{@code null} if unknown.
          * @return the exception if the operation failed, or {@code null} if 
none. This exception is not thrown now
          *         because the caller may succeed in creating the transform 
anyway, or otherwise may produce a more
          *         informative exception.
-         * @throws IllegalArgumentException if the operation fails because a 
parameter has a unrecognized name or an
-         *         illegal value.
          *
          * @see #getCompletedParameters()
          */
-        final RuntimeException completeParameters(OperationMethod method, 
final ParameterValueGroup userParams)
-                throws FactoryException, IllegalArgumentException
-        {
-            /*
-             * The "Geographic/geocentric conversions" conversion (EPSG:9602) 
can be either:
-             *
-             *    - "Ellipsoid_To_Geocentric"
-             *    - "Geocentric_To_Ellipsoid"
-             *
-             * EPSG defines both by a single operation, but Apache SIS needs 
to distinguish them.
-             */
-            if (method instanceof AbstractProvider) {
-                final String alt = ((AbstractProvider) 
method).resolveAmbiguity(this);
-                if (alt != null) {
-                    method = factory.getOperationMethod(alt);
-                }
-            }
-            provider   = method;
-            parameters = userParams;
-            ensureCompatibleParameters(false);      // Invoke only after we 
set `provider` to its final instance.
+        private RuntimeException completeParameters() throws 
IllegalArgumentException {
+            completedParameters = true;     // Need to be first.
             /*
              * Get a mask telling us if we need to set parameters for the 
source and/or target ellipsoid.
              * This information should preferably be given by the provider. 
But if the given provider is
@@ -1128,10 +1239,65 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
             return failure;
         }
 
+        /**
+         * Creates the parameterized transform. The operation method is given 
by {@link #getMethod()}
+         * and the parameter values should have been set on the group returned 
by {@link #parameters()}
+         * before to invoke this constructor.
+         *
+         * @return the parameterized transform.
+         * @throws FactoryException if the transform creation failed.
+         * This exception is thrown if some required parameters have not been 
supplied, or have illegal values.
+         *
+         * @since 1.5
+         */
+        @Override
+        public MathTransform create() throws FactoryException {
+            try {
+                if (provider instanceof AbstractProvider) {
+                    /*
+                     * The "Geographic/geocentric conversions" conversion 
(EPSG:9602) can be either:
+                     *
+                     *    - "Ellipsoid_To_Geocentric"
+                     *    - "Geocentric_To_Ellipsoid"
+                     *
+                     * EPSG defines both by a single operation, but Apache SIS 
needs to distinguish them.
+                     */
+                    final String method = ((AbstractProvider) 
provider).resolveAmbiguity(this);
+                    if (method != null) {
+                        provider = factory.getOperationMethod(method);
+                    }
+                }
+                final MathTransform transform;
+                if (provider instanceof MathTransformProvider) try {
+                    transform = ((MathTransformProvider) 
provider).createMathTransform(this);
+                } catch (IllegalArgumentException | IllegalStateException 
exception) {
+                    throw new 
InvalidGeodeticParameterException(exception.getLocalizedMessage(), exception);
+                } else {
+                    throw new UnimplementedServiceException(Errors.format(
+                            Errors.Keys.UnsupportedImplementation_1, 
Classes.getClass(provider)));
+                }
+                /*
+                 * Make sure that the number of dimensions is compatible with 
the OperationMethod instance.
+                 * Then make final adjustment for axis directions and units of 
measurement.
+                 */
+                if (provider instanceof AbstractProvider) {
+                    provider = ((AbstractProvider) 
provider).variantFor(transform);
+                }
+                return factory.unique(factory.swapAndScaleAxes(transform, 
this));
+            } catch (FactoryException exception) {
+                if (warning != null) {
+                    exception.addSuppressed(warning);
+                }
+                throw exception;
+            } finally {
+                factory.lastMethod.set(provider);
+            }
+        }
+
         /**
          * Returns a string representation of this context for debugging 
purposes.
-         * Current implementation write the name of source/target coordinate 
systems and ellipsoids.
-         * If {@linkplain #getContextualParameters() contextual parameters} 
have already been inferred,
+         * The current implementation writes the name of source/target 
coordinate systems and ellipsoids.
+         * If the {@linkplain #getContextualParameters() contextual 
parameters} have already been inferred,
          * then their names are appended with inconsistent parameters (if any) 
written on a separated line.
          *
          * @return a string representation of this context.
@@ -1186,32 +1352,11 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
      *     }
      *
      * Sometimes the {@code "semi_major"} and {@code "semi_minor"} parameter 
values are not explicitly provided,
-     * but rather inferred from the {@linkplain 
org.apache.sis.referencing.datum.DefaultGeodeticDatum geodetic
-     * datum} of the source Coordinate Reference System. If the given {@code 
context} argument is non-null,
-     * then this method will use those contextual information for:
-     *
-     * <ol>
-     *   <li>Inferring the {@code "semi_major"}, {@code "semi_minor"}, {@code 
"src_semi_major"},
-     *       {@code "src_semi_minor"}, {@code "tgt_semi_major"} or {@code 
"tgt_semi_minor"} parameter values
-     *       from the {@linkplain 
org.apache.sis.referencing.datum.DefaultEllipsoid ellipsoids} associated to
-     *       the source or target CRS, if those parameters are not explicitly 
given and if they are relevant
-     *       for the coordinate operation method.</li>
-     *   <li>{@linkplain #createConcatenatedTransform Concatenating} the 
parameterized transform
-     *       with any other transforms required for performing units changes 
and coordinates swapping.</li>
-     * </ol>
-     *
-     * The complete group of parameters, including {@code "semi_major"}, 
{@code "semi_minor"} or other calculated values,
-     * can be obtained by a call to {@link Context#getCompletedParameters()} 
after {@code createParameterizedTransform(…)}
-     * returned. Note that the completed parameters may only have additional 
parameters compared to the given parameter
-     * group; existing parameter values should not be modified.
-     *
-     * <p>The {@code OperationMethod} instance used by this constructor can be 
obtained by a call to
-     * {@link #getLastMethodUsed()}.</p>
+     * but rather inferred from the {@linkplain 
org.opengis.referencing.datum.GeodeticDatum geodetic
+     * reference frame} of the source Coordinate Reference System.
      *
      * @param  parameters  the parameter values. The {@linkplain 
ParameterDescriptorGroup#getName() parameter group name}
-     *                     shall be the name of the desired {@linkplain 
DefaultOperationMethod operation method}.
-     * @param  context     information about the context (for example source 
and target coordinate systems)
-     *                     in which the new transform is going to be used, or 
{@code null} if none.
+     *         shall be the name of the desired {@linkplain 
DefaultOperationMethod operation method}.
      * @return the transform created from the given parameters.
      * @throws NoSuchIdentifierException if there is no method for the given 
parameter group name.
      * @throws FactoryException if the object creation failed. This exception 
is thrown
@@ -1222,8 +1367,9 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
      * @see #getLastMethodUsed()
      * @see 
org.apache.sis.parameter.ParameterBuilder#createGroupForMapProjection(ParameterDescriptor...)
      */
-    public MathTransform createParameterizedTransform(final 
ParameterValueGroup parameters,
-            final Context context) throws NoSuchIdentifierException, 
FactoryException
+    @Override
+    public MathTransform createParameterizedTransform(final 
ParameterValueGroup parameters)
+            throws NoSuchIdentifierException, FactoryException
     {
         OperationMethod  method  = null;
         RuntimeException failure = null;
@@ -1247,50 +1393,30 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
             } catch (NoSuchIdentifierException exception) {
                 if (methodIdentifier.equals(methodName)) {
                     throw exception;
+                } else try {
+                    method = getOperationMethod(methodName);
+                } catch (NoSuchIdentifierException e) {
+                    e.addSuppressed(exception);
+                    throw e;
                 }
-                method = getOperationMethod(methodName);
                 Logging.recoverableException(AbstractMathTransform.LOGGER,
                         DefaultMathTransformFactory.class, 
"createParameterizedTransform", exception);
             }
-            if (!(method instanceof MathTransformProvider)) {
-                throw new NoSuchIdentifierException(Errors.format(          // 
For now, handle like an unknown operation.
-                        Errors.Keys.UnsupportedImplementation_1, 
Classes.getClass(method)), methodName);
-            }
             /*
              * Will catch only exceptions that may be the result of improper 
parameter usage (e.g. a value out
              * of range). Do not catch exceptions caused by programming errors 
(e.g. null pointer exception).
              */
-            try {
-                /*
-                 * If the user's parameters do not contain semi-major and 
semi-minor axis lengths, infer
-                 * them from the ellipsoid. We have to do that because those 
parameters are often omitted,
-                 * since the standard place where to provide this information 
is in the ellipsoid object.
-                 */
-                if (context != null) {
-                    context.factory = this;
-                    failure = context.completeParameters(method, parameters);
-                    method  = context.provider;
-                    transform = ((MathTransformProvider) 
method).createMathTransform(context);
-                } else {
-                    transform = ((MathTransformProvider) 
method).createMathTransform(this, parameters);
-                }
+            if (method instanceof MathTransformProvider) try {
+                transform = ((MathTransformProvider) 
method).createMathTransform(this, parameters);
             } catch (IllegalArgumentException | IllegalStateException 
exception) {
                 throw new 
InvalidGeodeticParameterException(exception.getLocalizedMessage(), exception);
+            } else {
+                throw new UnimplementedServiceException(Errors.format(
+                        Errors.Keys.UnsupportedImplementation_1, 
Classes.getClass(method)));
             }
-            /*
-             * Cache the transform that we just created and make sure that the 
number of dimensions
-             * is compatible with the OperationMethod instance. Then make 
final adjustment for axis
-             * directions and units of measurement.
-             */
             transform = unique(transform);
             if (method instanceof AbstractProvider) {
                 method = ((AbstractProvider) method).variantFor(transform);
-                if (context != null) {
-                    context.provider = method;
-                }
-            }
-            if (context != null) {
-                transform = swapAndScaleAxes(transform, context);
             }
         } catch (FactoryException e) {
             if (failure != null) {
@@ -1303,6 +1429,52 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         return transform;
     }
 
+    /**
+     * Creates a transform from a group of parameters and a context.
+     *
+     * @param  parameters  the parameter values.
+     * @param  context     information about the context, or {@code null} if 
none.
+     * @return the transform created from the given parameters.
+     * @throws NoSuchIdentifierException if there is no method for the given 
parameter group name.
+     * @throws FactoryException if the object creation failed.
+     * @deprecated Replaced by a builder pattern with {@link #builder(String)}.
+     */
+    @Deprecated(since="1.5", forRemoval=true)
+    public MathTransform createParameterizedTransform(final 
ParameterValueGroup parameters,
+            final Context context) throws NoSuchIdentifierException, 
FactoryException
+    {
+        // TODO: After the deprecated methods have been removed
+        //   - Make the `Context.parameters` field final (initialized in 
constructor).
+        //   - Delete `Context.ensureCompatibleParameters(boolean)`.
+        //   - Remove the null check on `Context.getCompletedParameters()` 
method body.
+        if (context == null) {
+            return createParameterizedTransform(parameters);
+        }
+        final ParameterDescriptorGroup descriptor = parameters.getDescriptor();
+        final String methodName = descriptor.getName().getCode();
+        String methodIdentifier = 
IdentifiedObjects.toString(IdentifiedObjects.getIdentifier(descriptor, 
Citations.EPSG));
+        if (methodIdentifier == null) {
+            methodIdentifier = methodName;
+        }
+        OperationMethod method;
+        try {
+            method = getOperationMethod(methodIdentifier);
+        } catch (NoSuchIdentifierException exception) {
+            if (methodIdentifier.equals(methodName)) {
+                throw exception;
+            }
+            method = getOperationMethod(methodName);
+            Logging.recoverableException(AbstractMathTransform.LOGGER,
+                    DefaultMathTransformFactory.class, 
"createParameterizedTransform", exception);
+        }
+        context.factory    = this;
+        context.provider   = method;
+        context.parameters = parameters;
+        context.completedParameters = false;
+        context.ensureCompatibleParameters(false);
+        return context.create();
+    }
+
     /**
      * Given a transform between normalized spaces,
      * creates a transform taking in account axis directions, units of 
measurement and longitude rotation.
@@ -1509,9 +1681,9 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         ArgumentChecks.ensureNonNull("baseCRS",    baseCRS);
         ArgumentChecks.ensureNonNull("parameters", parameters);
         ArgumentChecks.ensureNonNull("derivedCS",  derivedCS);
-        final Context context = 
ReferencingUtilities.createTransformContext(baseCRS, null);
-        context.setTarget(derivedCS);
-        return createParameterizedTransform(parameters, context);
+        var builder = ReferencingUtilities.builder(this, parameters, baseCRS, 
null);
+        builder.setTargetAxes(derivedCS, null);
+        return builder.create();
     }
 
     /**
@@ -1551,22 +1723,17 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
                  * EllipsoidalCS and SphericalCS or other coordinate systems.
                  */
                 if ((isEllipsoidalSource ? target : source) instanceof 
CartesianCS) {
-                    final Context context = new Context();
-                    final EllipsoidalCS cs;
-                    final String operation;
+                    final var context = new Context(this, getOperationMethod(
+                            isEllipsoidalSource ? GeographicToGeocentric.NAME
+                                                : 
GeocentricToGeographic.NAME));
                     if (isEllipsoidalSource) {
-                        operation = GeographicToGeocentric.NAME;
-                        context.setSource(cs = (EllipsoidalCS) source);
-                        context.setTarget(target);
-                        context.sourceEllipsoid = ellipsoid;
+                        context.setSourceAxes(source, ellipsoid);
+                        context.setTargetAxes(target, null);
                     } else {
-                        operation = GeocentricToGeographic.NAME;
-                        context.setSource(source);
-                        context.setTarget(cs = (EllipsoidalCS) target);
-                        context.targetEllipsoid = ellipsoid;
+                        context.setSourceAxes(source, null);
+                        context.setTargetAxes(target, ellipsoid);
                     }
-                    final ParameterValueGroup pg = 
getDefaultParameters(operation);
-                    return createParameterizedTransform(pg, context);
+                    return context.create();
                 }
             }
         }
@@ -1764,8 +1931,11 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
      *
      * @see #createParameterizedTransform(ParameterValueGroup, Context)
      * @see Context#getMethodUsed()
+     *
+     * @deprecated Replaced by {@link MathTransform.Builder#getMethod()}.
      */
     @Override
+    @Deprecated(since = "1.5")
     public OperationMethod getLastMethodUsed() {
         return lastMethod.get();
     }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
index 3179cdfb67..d7dc4b2150 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java
@@ -54,7 +54,8 @@ import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
 import org.apache.sis.referencing.internal.VerticalDatumTypes;
 import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
-import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory.Context;
+import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
+import org.apache.sis.parameter.Parameters;
 
 
 /**
@@ -511,58 +512,35 @@ public final class ReferencingUtilities extends Static {
     }
 
     /**
-     * Creates a context with source and target ellipsoids and coordinate 
systems inferred from the given CRS.
+     * Creates a builder with source and target ellipsoids and coordinate 
systems inferred from the given CRSs.
      * The ellipsoids will be non-null only if the given CRS is geodetic 
(geographic or geocentric).
      *
-     * @param  sourceCRS  the CRS from which to get the source coordinate 
system and ellipsoid, or {@code null}.
-     * @param  targetCRS  the CRS from which to get the target coordinate 
system and ellipsoid, or {@code null}.
+     * @param  factory     the factory on which to create the builder.
+     * @param  parameters  the operation parameter value group.
+     * @param  sourceCRS   the CRS from which to get the source coordinate 
system and ellipsoid, or {@code null}.
+     * @param  targetCRS   the CRS from which to get the target coordinate 
system and ellipsoid, or {@code null}.
      * @return the context to provides to math transform factory.
+     * @throws FactoryException if the builder cannot be created.
      */
-    public static Context createTransformContext(final 
CoordinateReferenceSystem sourceCRS,
-                                                 final 
CoordinateReferenceSystem targetCRS)
+    public static MathTransform.Builder builder(
+            final MathTransformFactory factory,
+            final ParameterValueGroup parameters,
+            final CoordinateReferenceSystem sourceCRS,
+            final CoordinateReferenceSystem targetCRS) throws FactoryException
     {
-        final Context context = new Context();
-        if (sourceCRS instanceof GeodeticCRS) {
-            context.setSource((GeodeticCRS) sourceCRS);
-        } else if (sourceCRS != null) {
-            context.setSource(sourceCRS.getCoordinateSystem());
+        final var builder = 
factory.builder(parameters.getDescriptor().getName().getCode());
+        try {
+            Parameters.copy(parameters, builder.parameters());
+        } catch (IllegalArgumentException e) {
+            throw new InvalidGeodeticParameterException(e.getMessage(), e);
         }
-        if (targetCRS instanceof GeodeticCRS) {
-            context.setTarget((GeodeticCRS) targetCRS);
-        } else if (targetCRS != null) {
-            context.setTarget(targetCRS.getCoordinateSystem());
+        if (sourceCRS != null) {
+            builder.setSourceAxes(sourceCRS.getCoordinateSystem(), 
getEllipsoid(sourceCRS));
         }
-        return context;
-    }
-
-    /**
-     * Substitute for the deprecated {@link 
MathTransformFactory#createBaseToDerived createBaseToDerived(…)} method.
-     * This substitute uses the full {@code targetCRS} instead of only the 
coordinate system of the target.
-     * This is needed for setting the {@code "tgt_semi_minor"} and {@code 
"tgt_semi_major"} parameters of
-     * Molodensky transformation for example.
-     *
-     * @param  factory     the factory to use for creating the transform.
-     * @param  sourceCRS   the source (base) coordinate reference system.
-     * @param  parameters  the parameter values for the transform.
-     * @param  targetCRS   the target (derived) coordinate system.
-     * @return the parameterized transform from {@code sourceCRS} to {@code 
targetCRS},
-     *         including unit conversions and axis swapping.
-     * @throws FactoryException if the object creation failed. This exception 
is thrown
-     *         if some required parameter has not been supplied, or has 
illegal value.
-     *
-     * @see <a href="https://issues.apache.org/jira/browse/SIS-512";>SIS-512 on 
issues tracker</a>
-     */
-    public static MathTransform createBaseToDerived(final MathTransformFactory 
factory,
-            final CoordinateReferenceSystem sourceCRS, final 
ParameterValueGroup parameters,
-            final CoordinateReferenceSystem targetCRS) throws FactoryException
-    {
-        if (factory instanceof DefaultMathTransformFactory) {
-            return ((DefaultMathTransformFactory) 
factory).createParameterizedTransform(
-                    parameters, createTransformContext(sourceCRS, targetCRS));
-        } else {
-            // Fallback for non-SIS implementations. Work for map projections 
but not for Molodensky.
-            return factory.createBaseToDerived(sourceCRS, parameters, 
targetCRS.getCoordinateSystem());
+        if (targetCRS != null) {
+            builder.setTargetAxes(targetCRS.getCoordinateSystem(), 
getEllipsoid(targetCRS));
         }
+        return builder;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeographicOffsetsTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeographicOffsetsTest.java
index 94b9949288..755c275c6f 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeographicOffsetsTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeographicOffsetsTest.java
@@ -124,19 +124,18 @@ public final class GeographicOffsetsTest extends 
TransformTestCase {
     @Test
     public void testCreateWithContext() throws FactoryException, 
TransformException {
         final DefaultMathTransformFactory factory = 
DefaultMathTransformFactory.provider();
-        final ParameterValueGroup pv = factory.getDefaultParameters("Vertical 
Offset");
-        pv.parameter("Vertical Offset").setValue(15.55, Units.FOOT);
         /*
          * Now create the MathTransform. But at the difference of the above 
testVerticalOffset() method,
          * we supply information about axis directions. The operation 
parameter shall have the same sign
-         * than in the EPSG database (which is positive), and the source and 
target coordinates shall have
+         * as in the EPSG database (which is positive), and the source and 
target coordinates shall have
          * the same sign as in the EPSG example (positive too). However, we do 
not test unit conversion
          * in this method (EPSG sample point uses feet units), only axis 
direction.
          */
-        final DefaultMathTransformFactory.Context context = new 
DefaultMathTransformFactory.Context();
-        context.setSource(HardCodedCS.GRAVITY_RELATED_HEIGHT);  // Direction 
up, in metres.
-        context.setTarget(HardCodedCS.DEPTH);                   // Direction 
down, in metres.
-        transform = factory.createParameterizedTransform(pv, context);
+        final var builder = factory.builder("Vertical Offset");
+        builder.parameters().parameter("Vertical Offset").setValue(15.55, 
Units.FOOT);
+        builder.setSourceAxes(HardCodedCS.GRAVITY_RELATED_HEIGHT, null);  // 
Direction up, in metres.
+        builder.setTargetAxes(HardCodedCS.DEPTH, null);                   // 
Direction down, in metres.
+        transform = builder.create();
         tolerance = Formulas.LINEAR_TOLERANCE;
         final double[] source = new double[transform.getSourceDimensions()];
         final double[] target = new double[transform.getTargetDimensions()];
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
index 5a29446915..b75449db34 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
@@ -310,9 +310,9 @@ public final class DefaultMathTransformFactoryTest extends 
TestCase {
     @Test
     public void testSwapAndScaleAxes() throws FactoryException {
         final DefaultMathTransformFactory factory = factory();
-        final DefaultMathTransformFactory.Context context = new 
DefaultMathTransformFactory.Context();
-        context.setSource(HardCodedCS.GEODETIC_3D);
-        context.setTarget(HardCodedCS.CARTESIAN_3D);
+        final var context = new DefaultMathTransformFactory.Context();
+        context.setSourceAxes(HardCodedCS.GEODETIC_3D,  null);
+        context.setTargetAxes(HardCodedCS.CARTESIAN_3D, null);
         /*
          * Simulate a case where the parameterized transform is a 
two-dimensional map projection,
          * but the input and output CRS are three-dimensional geographic and 
projected CRS respectively.
@@ -324,8 +324,8 @@ public final class DefaultMathTransformFactoryTest extends 
TestCase {
         /*
          * Transform from 3D to 2D. Height dimension is dropped.
          */
-        context.setSource(HardCodedCS.GEODETIC_3D);
-        context.setTarget(HardCodedCS.GEODETIC_2D);
+        context.setSourceAxes(HardCodedCS.GEODETIC_3D, null);
+        context.setTargetAxes(HardCodedCS.GEODETIC_2D, null);
         mt = factory.swapAndScaleAxes(MathTransforms.identity(2), context);
         var expected = Matrices.create(3, 4, new double[] {
             1, 0, 0, 0,
@@ -337,8 +337,8 @@ public final class DefaultMathTransformFactoryTest extends 
TestCase {
          * Transform from 2D to 3D. Coordinate values in the height dimension 
are unknown (NaN).
          * This case happen when the third dimension is handled as a "pass 
through" dimension.
          */
-        context.setSource(HardCodedCS.GEODETIC_2D);
-        context.setTarget(HardCodedCS.GEODETIC_3D);
+        context.setSourceAxes(HardCodedCS.GEODETIC_2D, null);
+        context.setTargetAxes(HardCodedCS.GEODETIC_3D, null);
         mt = factory.swapAndScaleAxes(MathTransforms.identity(2), context);
         expected = Matrices.create(4, 3, new double[] {
             1, 0, 0,
@@ -362,8 +362,8 @@ public final class DefaultMathTransformFactoryTest extends 
TestCase {
         /*
          * Test error message when adding a dimension that is not ellipsoidal 
height.
          */
-        context.setSource(HardCodedCS.CARTESIAN_2D);
-        context.setTarget(HardCodedCS.CARTESIAN_3D);
+        context.setSourceAxes(HardCodedCS.CARTESIAN_2D, null);
+        context.setTargetAxes(HardCodedCS.CARTESIAN_3D, null);
         var e = assertThrows(InvalidGeodeticParameterException.class,
                 () -> factory.swapAndScaleAxes(MathTransforms.identity(2), 
context),
                 "Should not have accepted the given coordinate systems.");
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
index b8c252b7d0..84412badde 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
@@ -65,6 +65,7 @@ class MathTransformFactoryBase implements 
MathTransformFactory {
 
     /** Default implementation unconditionally returns {@code null}. */
     @Override
+    @Deprecated
     public OperationMethod getLastMethodUsed() {
         return null;
     }
@@ -77,10 +78,17 @@ class MathTransformFactoryBase implements 
MathTransformFactory {
 
     /** Default implementation throws an exception. */
     @Override
+    @Deprecated
     public MathTransform createBaseToDerived(CoordinateReferenceSystem 
baseCRS, ParameterValueGroup parameters, CoordinateSystem derivedCS) throws 
FactoryException {
         throw new FactoryException(MESSAGE);
     }
 
+    /** Default implementation throws an exception. */
+    @Override
+    public MathTransform.Builder builder(String code) throws 
NoSuchIdentifierException {
+        throw new NoSuchIdentifierException(MESSAGE, code);
+    }
+
     /** Default implementation throws an exception. */
     @Override
     public MathTransform createParameterizedTransform(ParameterValueGroup 
parameters) throws FactoryException {
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
index 762fce44e2..f368841e2d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
@@ -102,6 +102,7 @@ public final class MathTransformFactoryMock implements 
MathTransformFactory {
      * @return the method given at construction time.
      */
     @Override
+    @Deprecated
     public OperationMethod getLastMethodUsed() {
         return method;
     }
@@ -192,12 +193,24 @@ public final class MathTransformFactoryMock implements 
MathTransformFactory {
      * @return never returned.
      */
     @Override
+    @Deprecated
     public MathTransform createBaseToDerived(CoordinateReferenceSystem baseCRS,
             ParameterValueGroup parameters, CoordinateSystem derivedCS)
     {
         throw new UnsupportedOperationException();
     }
 
+    /**
+     * Unimplemented method.
+     *
+     * @param  code  ignored.
+     * @return never returned.
+     */
+    @Override
+    public MathTransform.Builder builder(String code) {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * Unimplemented method.
      *
diff --git a/geoapi/snapshot b/geoapi/snapshot
index a0ae30dd67..e57e817bb1 160000
--- a/geoapi/snapshot
+++ b/geoapi/snapshot
@@ -1 +1 @@
-Subproject commit a0ae30dd67b67a718b74bd8a90140ea0a559e8b6
+Subproject commit e57e817bb1965d241d4f16f498499d6bb304fddd

Reply via email to