sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1724531 [6/13] - in /sis/trunk: ./ application/sis-console/src/main/artifact/bin/ application/sis-console/src/main/artifact/log/ application/sis-console/src/main/java/org/apache/sis/console/ core/sis-build-helper/src/main/java/org/apache/s...
Date Wed, 13 Jan 2016 23:59:41 GMT
Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -17,7 +17,6 @@
 package org.apache.sis.referencing.operation.transform;
 
 import java.util.IdentityHashMap;
-import java.util.Locale;
 import java.util.Map;
 import java.util.ServiceLoader;
 import java.util.Set;
@@ -27,6 +26,7 @@ import java.util.concurrent.atomic.Atomi
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.lang.reflect.Constructor;
+import java.io.Serializable;
 import javax.measure.quantity.Length;
 import javax.measure.unit.SI;
 import javax.measure.unit.Unit;
@@ -34,11 +34,11 @@ import javax.measure.converter.Conversio
 
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.ParameterNotFoundException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
@@ -55,9 +55,16 @@ import org.apache.sis.internal.referenci
 import org.apache.sis.internal.metadata.ReferencingServices;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.referencing.j2d.ParameterizedAffine;
+import org.apache.sis.internal.referencing.provider.AbstractProvider;
+import org.apache.sis.internal.referencing.provider.VerticalOffset;
 import org.apache.sis.internal.system.Loggers;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.parameter.DefaultParameterValueGroup;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.CoordinateSystems;
+import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
 import org.apache.sis.referencing.operation.DefaultOperationMethod;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.util.ArgumentChecks;
@@ -82,8 +89,8 @@ import org.apache.sis.internal.jdk8.JDK8
  *
  *
  * <div class="section">Standard parameters</div>
- * {@code MathTransform} instances are created from {@linkplain org.apache.sis.parameter.DefaultParameterValueGroup
- * parameter values}. The parameters expected by each operation available in a default Apache SIS installation is
+ * {@code MathTransform} instances are created from {@linkplain DefaultParameterValueGroup parameter values}.
+ * The parameters expected by each operation available in a default Apache SIS installation is
  * <a href="http://sis.apache.org/book/tables/CoordinateOperationMethods.html">listed here</a>.
  * The set of parameters varies for each operation or projection, but the following can be considered typical:
  *
@@ -102,7 +109,7 @@ import org.apache.sis.internal.jdk8.JDK8
  * <div class="section">Dynamic parameters</div>
  * A few non-standard parameters are defined for compatibility reasons,
  * but delegates their work to standard parameters. Those dynamic parameters are not listed in the
- * {@linkplain org.apache.sis.parameter.DefaultParameterValueGroup#values() parameter values}.
+ * {@linkplain DefaultParameterValueGroup#values() parameter values}.
  * Dynamic parameters are:
  *
  * <ul>
@@ -323,7 +330,7 @@ public class DefaultMathTransformFactory
      * @return Methods available in this factory for coordinate operations of the given type.
      *
      * @see #getDefaultParameters(String)
-     * @see #createParameterizedTransform(ParameterValueGroup)
+     * @see #createParameterizedTransform(ParameterValueGroup, Context)
      * @see DefaultOperationMethod#getOperationType()
      */
     @Override
@@ -400,15 +407,15 @@ public class DefaultMathTransformFactory
      *
      * <p>This function creates new parameter instances at every call.
      * Parameters are intended to be modified by the user before to be given to the
-     * {@link #createParameterizedTransform createParameterizedTransform(…)} constructor.</p>
+     * {@link #createParameterizedTransform createParameterizedTransform(…)} method.</p>
      *
      * @param  method The case insensitive name of the coordinate operation method to search for.
      * @return A new group of parameter values for the {@code OperationMethod} identified by the given name.
      * @throws NoSuchIdentifierException if there is no method registered for the given name or identifier.
      *
      * @see #getAvailableMethods(Class)
-     * @see #createParameterizedTransform(ParameterValueGroup)
-     * @see org.apache.sis.referencing.operation.transform.AbstractMathTransform#getParameterValues()
+     * @see #createParameterizedTransform(ParameterValueGroup, Context)
+     * @see AbstractMathTransform#getParameterValues()
      */
     @Override
     public ParameterValueGroup getDefaultParameters(final String method) throws NoSuchIdentifierException {
@@ -416,183 +423,544 @@ public class DefaultMathTransformFactory
     }
 
     /**
-     * Returns the value of the given parameter in the given unit, or {@code NaN} if the parameter is not set.
+     * 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.
      *
-     * <p><b>NOTE:</b> Do not merge this function with {@code ensureSet(…)}. We keep those two methods
-     * separated in order to give to {@code createBaseToDerived(…)} a "all or nothing" behavior.</p>
+     * @deprecated Replaced by {@link #createParameterizedTransform(ParameterValueGroup, Context)}
+     *             where the {@code Context} argument can be null.
      */
-    private static double getValue(final ParameterValue<?> parameter, final Unit<Length> unit) {
-        return (parameter.getValue() != null) ? parameter.doubleValue(unit) : Double.NaN;
+    @Override
+    @Deprecated
+    public MathTransform createParameterizedTransform(final ParameterValueGroup parameters)
+            throws NoSuchIdentifierException, FactoryException
+    {
+        return createParameterizedTransform(parameters, null);
     }
 
     /**
-     * Ensures that a value is set in the given parameter.
+     * Source and target coordinate systems for which a new parameterized transform is going to be used.
+     * {@link DefaultMathTransformFactory} uses this information for:
      *
      * <ul>
-     *   <li>If the parameter has no value, then it is set to the given value.<li>
-     *   <li>If the parameter already has a value, then the parameter is left unchanged
-     *       but its value is compared to the given one for consistency.</li>
+     *   <li>Complete 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 Swap and scale axes} if the source or the target
+     *       coordinate systems are not {@linkplain AxesConvention#NORMALIZED normalized}.</li>
      * </ul>
      *
-     * @param parameter The parameter which must have a value.
-     * @param actual    The current parameter value, or {@code NaN} if none.
-     * @param expected  The expected parameter value, derived from the ellipsoid.
-     * @param unit      The unit of {@code value}.
-     * @param tolerance Maximal difference (in unit of {@code unit}) for considering the two values as equivalent.
-     * @return {@code true} if there is a mismatch between the actual value and the expected one.
-     */
-    private static boolean ensureSet(final ParameterValue<?> parameter, final double actual,
-            final double expected, final Unit<?> unit, final double tolerance)
-    {
-        if (Math.abs(actual - expected) <= tolerance) {
-            return false;
+     * @author  Martin Desruisseaux (Geomatys)
+     * @version 0.7
+     * @since   0.7
+     * @module
+     */
+    public static class Context implements Serializable {
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = 6963581151055917955L;
+
+        /**
+         * Coordinate system of the source or target points.
+         */
+        private CoordinateSystem sourceCS, targetCS;
+
+        /**
+         * 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(…)}.
+         *
+         * @todo We could make this information public as a replacement of {@link #getLastMethodUsed()}.
+         */
+        OperationMethod provider;
+
+        /**
+         * The parameters actually used.
+         *
+         * @see #getCompletedParameters()
+         */
+        ParameterValueGroup parameters;
+
+        /**
+         * Creates a new context with all properties initialized to {@code null}.
+         */
+        public Context() {
+        }
+
+        /**
+         * Sets the source ellipsoid to the given value.
+         * The source coordinate system is unconditionally set to {@code null}.
+         *
+         * @param ellipsoid The ellipsoid to set as the source (can be {@code null}).
+         */
+        public void setSource(final Ellipsoid ellipsoid) {
+            sourceEllipsoid = ellipsoid;
+            sourceCS = null;
+        }
+
+        /**
+         * 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}).
+         */
+        public void setSource(final CoordinateSystem cs) {
+            sourceCS = cs;
+            sourceEllipsoid = null;
+        }
+
+        /**
+         * Sets the source ellipsoid and coordinate system to values inferred from the given CRS.
+         * The source ellipsoid will be non-null only if the given CRS is geographic (not geocentric).
+         *
+         * @param crs The source coordinate reference system (can be {@code null}).
+         */
+        public void setSource(final CoordinateReferenceSystem crs) {
+            sourceCS = (crs != null) ? crs.getCoordinateSystem() : null;
+            sourceEllipsoid = ReferencingUtilities.getEllipsoidOfGeographicCRS(crs);
+            // Ellipsoid is intentionally null for GeocentricCRS.
+        }
+
+        /**
+         * Sets the target ellipsoid to the given value.
+         * The target coordinate system is unconditionally set to {@code null}.
+         *
+         * @param ellipsoid The ellipsoid to set as the target (can be {@code null}).
+         */
+        public void setTarget(final Ellipsoid ellipsoid) {
+            targetEllipsoid = ellipsoid;
+            targetCS = null;
+        }
+
+        /**
+         * Sets the target coordinate system to the given value.
+         * The target ellipsoid is unconditionally set to {@code null}.
+         *
+         * @param cs The coordinate system to set as the target (can be {@code null}).
+         */
+        public void setTarget(final CoordinateSystem cs) {
+            targetCS = cs;
+            targetEllipsoid = null;
+        }
+
+        /**
+         * Sets the target ellipsoid and coordinate system to values inferred from the given CRS.
+         * The target ellipsoid will be non-null only if the given CRS is geographic (not geocentric).
+         *
+         * @param crs The target coordinate reference system (can be {@code null}).
+         */
+        public void setTarget(final CoordinateReferenceSystem crs) {
+            targetCS = (crs != null) ? crs.getCoordinateSystem() : null;
+            targetEllipsoid = ReferencingUtilities.getEllipsoidOfGeographicCRS(crs);
+            // Ellipsoid is intentionally null for GeocentricCRS.
+        }
+
+        /**
+         * Returns the source coordinate system, or {@code null} if unspecified.
+         *
+         * @return The source coordinate system, or {@code null}.
+         */
+        public CoordinateSystem getSourceCS() {
+            return sourceCS;
+        }
+
+        /**
+         * 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}.
+         *
+         * @return the ellipsoid of the source ellipsoidal coordinate system, or {@code null} if it does not apply.
+         */
+        public Ellipsoid getSourceEllipsoid() {
+            return sourceEllipsoid;
+        }
+
+        /**
+         * Returns the target coordinate system, or {@code null} if unspecified.
+         *
+         * @return The target coordinate system, or {@code null}.
+         */
+        public CoordinateSystem getTargetCS() {
+            return targetCS;
+        }
+
+        /**
+         * 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}.
+         *
+         * @return the ellipsoid of the target ellipsoidal coordinate system, or {@code null} if it does not apply.
+         */
+        public Ellipsoid getTargetEllipsoid() {
+            return targetEllipsoid;
         }
-        if (Double.isNaN(actual)) {
-            parameter.setValue(expected, unit);
-            return false;
+
+        /**
+         * Returns the parameter values used for the math transform creation, including the parameters completed
+         * by the factory.
+         *
+         * @return The parameter values used by the factory.
+         * @throws IllegalStateException if {@link DefaultMathTransformFactory#createParameterizedTransform(ParameterValueGroup, Context)}
+         *         has not yet been invoked.
+         */
+        public ParameterValueGroup getCompletedParameters() {
+            if (parameters != null) {
+                return parameters;
+            }
+            throw new IllegalStateException(Errors.format(Errors.Keys.UnspecifiedParameterValues));
+        }
+
+        /**
+         * If the parameters given by the user were not created by {@code getDefaultParameters(String)}
+         * or something equivalent, copies those parameters into the structure expected by the provider.
+         * The intend is to make sure that we have room for the parameters that {@code setEllipsoids(…)}
+         * may write.
+         *
+         * @param writable {@code true} if this method should also check that the parameters group is not
+         *        an instance of {@link UnmodifiableParameterValueGroup}. Current implementation assumes
+         *        that modifiable parameters are instances of {@link DefaultParameterValueGroup}.
+         */
+        private void ensureCompatibleParameters(final boolean writable) {
+            final ParameterDescriptorGroup expected = provider.getParameters();
+            if (parameters.getDescriptor() != expected ||
+                    (writable &&  (parameters instanceof Parameters)
+                              && !(parameters instanceof DefaultParameterValueGroup)))
+            {
+                final ParameterValueGroup copy = expected.createValue();
+                Parameters.copy(parameters, copy);
+                parameters = copy;
+            }
+        }
+
+        /**
+         * Returns the value of the given parameter in the given unit, or {@code NaN} if the parameter is not set.
+         *
+         * <p><b>NOTE:</b> Do not merge this function with {@code ensureSet(…)}. We keep those two methods
+         * separated in order to give to {@code createParameterizedTransform(…)} a "all or nothing" behavior.</p>
+         */
+        private static double getValue(final ParameterValue<?> parameter, final Unit<Length> unit) {
+            return (parameter.getValue() != null) ? parameter.doubleValue(unit) : Double.NaN;
+        }
+
+        /**
+         * Ensures that a value is set in the given parameter.
+         *
+         * <ul>
+         *   <li>If the parameter has no value, then it is set to the given value.<li>
+         *   <li>If the parameter already has a value, then the parameter is left unchanged
+         *       but its value is compared to the given one for consistency.</li>
+         * </ul>
+         *
+         * @param parameter The parameter which must have a value.
+         * @param actual    The current parameter value, or {@code NaN} if none.
+         * @param expected  The expected parameter value, derived from the ellipsoid.
+         * @param unit      The unit of {@code value}.
+         * @param tolerance Maximal difference (in unit of {@code unit}) for considering the two values as equivalent.
+         * @return {@code true} if there is a mismatch between the actual value and the expected one.
+         */
+        private static boolean ensureSet(final ParameterValue<?> parameter, final double actual,
+                final double expected, final Unit<?> unit, final double tolerance)
+        {
+            if (Math.abs(actual - expected) <= tolerance) {
+                return false;
+            }
+            if (Double.isNaN(actual)) {
+                parameter.setValue(expected, unit);
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Completes the parameter group with information about source or 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.
+         *
+         * @param  ellipsoid  The ellipsoid from which to get axis lengths of flattening factor, or {@code null}.
+         * @param  semiMajor  {@code "semi_major}, {@code "src_semi_major} or {@code "tgt_semi_major} parameter name.
+         * @param  semiMinor  {@code "semi_minor}, {@code "src_semi_minor} or {@code "tgt_semi_minor} parameter name.
+         * @param  inverseFlattening {@code true} if this method can try to set the {@code "inverse_flattening"} parameter.
+         * @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.
+         */
+        private RuntimeException setEllipsoid(final Ellipsoid ellipsoid, final String semiMajor, final String semiMinor,
+                final boolean inverseFlattening, RuntimeException failure)
+        {
+            /*
+             * Note: we could also consider to set the "dim" parameter here based on the number of dimensions
+             * of the coordinate system. But except for the Molodensky operation, this would be SIS-specific.
+             * A more portable way is to concatenate a "Geographic 3D to 2D" operation after the transform if
+             * we see that the dimensions do not match. It also avoid attempt to set a "dim" parameter on map
+             * projections, which is not allowed.
+             */
+            if (ellipsoid != null) {
+                ensureCompatibleParameters(true);
+                ParameterValue<?> mismatchedParam = null;
+                double mismatchedValue = 0;
+                try {
+                    final ParameterValue<?> ap = parameters.parameter(semiMajor);
+                    final ParameterValue<?> bp = parameters.parameter(semiMinor);
+                    final Unit<Length> unit = ellipsoid.getAxisUnit();
+                    /*
+                     * The two calls to getValue(…) shall succeed before we write anything, in order to have a
+                     * "all or nothing" behavior as much as possible. Note that Ellipsoid.getSemi**Axis() have
+                     * no reason to fail, so we do not take precaution for them.
+                     */
+                    final double a   = getValue(ap, unit);
+                    final double b   = getValue(bp, unit);
+                    final double tol = SI.METRE.getConverterTo(unit).convert(ELLIPSOID_PRECISION);
+                    if (ensureSet(ap, a, ellipsoid.getSemiMajorAxis(), unit, tol)) {
+                        mismatchedParam = ap;
+                        mismatchedValue = a;
+                    }
+                    if (ensureSet(bp, b, ellipsoid.getSemiMinorAxis(), unit, tol)) {
+                        mismatchedParam = bp;
+                        mismatchedValue = b;
+                    }
+                } catch (RuntimeException e) {  // (IllegalArgumentException | IllegalStateException) on the JDK7 branch.
+                    /*
+                     * Parameter not found, or is not numeric, or unit of measurement is not linear.
+                     * Do not touch to the parameters. We will see if createParameterizedTransform(…)
+                     * can do something about that. If it can not, createParameterizedTransform(…) is
+                     * the right place to throw the exception.
+                     */
+                    if (failure == null) {
+                        failure = e;
+                    } else {
+                        // failure.addSuppressed(e) on the JDK7 branch.
+                    }
+                }
+                final boolean isIvfDefinitive;
+                if (mismatchedParam != null) {
+                    final LogRecord record = Messages.getResources(null).getLogRecord(Level.WARNING,
+                            Messages.Keys.MismatchedEllipsoidAxisLength_3, ellipsoid.getName().getCode(),
+                            mismatchedParam.getDescriptor().getName().getCode(), mismatchedValue);
+                    record.setLoggerName(Loggers.COORDINATE_OPERATION);
+                    Logging.log(DefaultMathTransformFactory.class, "createParameterizedTransform", record);
+                    isIvfDefinitive = false;
+                } else {
+                    isIvfDefinitive = inverseFlattening && ellipsoid.isIvfDefinitive();
+                }
+                /*
+                 * Following is specific to Apache SIS. We use this non-standard API for allowing the
+                 * NormalizedProjection class (our base class for all map projection implementations)
+                 * to known that the ellipsoid definitive parameter is the inverse flattening factor
+                 * instead than the semi-major axis length. It makes a small difference in the accuracy
+                 * of the eccentricity parameter.
+                 */
+                if (isIvfDefinitive) try {
+                    parameters.parameter(Constants.INVERSE_FLATTENING).setValue(ellipsoid.getInverseFlattening());
+                } catch (ParameterNotFoundException e) {
+                    /*
+                     * Should never happen with Apache SIS implementation, but may happen if the given parameters come
+                     * from another implementation. We can safely abandon our attempt to set the inverse flattening value,
+                     * since it was redundant with semi-minor axis length.
+                     */
+                    Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
+                            DefaultMathTransformFactory.class, "createParameterizedTransform", e);
+                }
+            }
+            return failure;
+        }
+
+        /**
+         * 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.
+         *
+         * @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.
+         */
+        final RuntimeException setEllipsoids(final OperationMethod method) {
+            ensureCompatibleParameters(false);
+            int n;
+            if (method instanceof AbstractProvider) {
+                n = ((AbstractProvider) method).getEllipsoidsMask();
+            } else {
+                // Fallback used only when the information is not available in
+                // a more reliable way from AbstractProvider.getEllipsoidsMask().
+                n = 0;
+                if (sourceEllipsoid != null) n  = 1;
+                if (targetEllipsoid != null) n |= 2;
+            }
+            switch (n) {
+                case 0: return null;
+                case 1: return setEllipsoid(getSourceEllipsoid(), Constants.SEMI_MAJOR, Constants.SEMI_MINOR, true, null);
+                case 2: return setEllipsoid(getTargetEllipsoid(), Constants.SEMI_MAJOR, Constants.SEMI_MINOR, true, null);
+                case 3: {
+                    RuntimeException failure = null;
+                    if (sourceCS != null) try {
+                        ensureCompatibleParameters(true);
+                        final ParameterValue<?> p = parameters.parameter("dim");
+                        if (p.getValue() == null) {
+                            p.setValue(sourceCS.getDimension());
+                        }
+                    } catch (RuntimeException e) {  // (IllegalArgumentException | IllegalStateException e) on the JDK7 branch.
+                        failure = e;
+                    }
+                    failure = setEllipsoid(getSourceEllipsoid(), "src_semi_major", "src_semi_minor", false, failure);
+                    failure = setEllipsoid(getTargetEllipsoid(), "tgt_semi_major", "tgt_semi_minor", false, failure);
+                    return failure;
+                }
+                default: throw new AssertionError(n);
+            }
         }
-        return true;
     }
 
     /**
-     * Creates a transform from a base CRS to a derived CS using the given parameters.
-     * This convenience constructor:
+     * Creates a transform from a group of parameters.
+     * The set of expected parameters varies for each operation.
+     * The easiest way to provide parameter values is to get an initially empty group for the desired
+     * operation by calling {@link #getDefaultParameters(String)}, then to fill the parameter values.
+     * Example:
+     *
+     * {@preformat java
+     *     ParameterValueGroup group = factory.getDefaultParameters("Transverse_Mercator");
+     *     group.parameter("semi_major").setValue(6378137.000);
+     *     group.parameter("semi_minor").setValue(6356752.314);
+     *     MathTransform mt = factory.createParameterizedTransform(group, null);
+     * }
+     *
+     * Sometime 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>Infers the {@code "semi_major"} and {@code "semi_minor"} parameters values from the
-     *       {@linkplain org.apache.sis.referencing.datum.DefaultEllipsoid ellipsoid} associated
-     *       to the {@code baseCRS}, if those parameters are not explicitly given and if they are
-     *       applicable (typically for cartographic projections).</li>
-     *   <li>{@linkplain #createConcatenatedTransform Concatenates} the
-     *       {@linkplain #createParameterizedTransform(ParameterValueGroup) parameterized transform}
-     *       with any other transform required for performing units changes and ordinates swapping.</li>
+     *   <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"} parameters 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 ordinates swapping.</li>
      * </ol>
      *
-     * The {@code OperationMethod} instance used by this constructor can be obtained by a call to
-     * {@link #getLastMethodUsed()}.
+     * 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.
      *
-     * <div class="section">Modification of given parameters</div>
-     * If this method needs to set the values of {@code "semi_major"} and {@code "semi_minor"} parameters,
-     * then it sets those values directly on the given {@code parameters} instance – not on a clone – for
-     * allowing the caller to get back the complete parameter values.
-     * However this method only fills missing values, it never modify existing values.
+     * <p>The {@code OperationMethod} instance used by this constructor can be obtained by a call to
+     * {@link #getLastMethodUsed()}.</p>
      *
-     * @param  baseCRS    The source coordinate reference system.
-     * @param  parameters The parameter values for the transform.
-     * @param  derivedCS  The target coordinate system.
-     * @return The parameterized transform from {@code baseCRS} to {@code derivedCS},
-     *         including unit conversions and axis swapping.
-     * @throws NoSuchIdentifierException if there is no transform registered for the coordinate operation method.
+     * @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.
+     * @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.
      *
+     * @see #getDefaultParameters(String)
+     * @see #getAvailableMethods(Class)
+     * @see #getLastMethodUsed()
      * @see org.apache.sis.parameter.ParameterBuilder#createGroupForMapProjection(ParameterDescriptor...)
      */
-    @Override
-    public MathTransform createBaseToDerived(final CoordinateReferenceSystem baseCRS,
-            final ParameterValueGroup parameters, final CoordinateSystem derivedCS)
-            throws NoSuchIdentifierException, FactoryException
+    public MathTransform createParameterizedTransform(ParameterValueGroup parameters,
+            final Context context) throws NoSuchIdentifierException, FactoryException
     {
-        ArgumentChecks.ensureNonNull("baseCRS",    baseCRS);
-        ArgumentChecks.ensureNonNull("parameters", parameters);
-        ArgumentChecks.ensureNonNull("derivedCS",  derivedCS);
-        /*
-         * 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.
-         */
+        OperationMethod  method  = null;
         RuntimeException failure = null;
-        final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoidOfGeographicCRS(baseCRS);
-        if (ellipsoid != null) {
-            ParameterValue<?> mismatchedParam = null;
-            double mismatchedValue = 0;
+        MathTransform transform;
+        try {
+            ArgumentChecks.ensureNonNull("parameters", 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;
+            }
+            /*
+             * Get the MathTransformProvider of the same name or identifier than the given parameter group.
+             * We give precedence to EPSG identifier because operation method names are sometime ambiguous
+             * (e.g. "Lambert Azimuthal Equal Area (Spherical)"). If we fail to find the method by its EPSG code,
+             * we will try searching by method name. As a side effect, this second attempt will produce a better
+             * error message if the method is really not found.
+             */
             try {
-                final ParameterValue<?> semiMajor = parameters.parameter(Constants.SEMI_MAJOR);
-                final ParameterValue<?> semiMinor = parameters.parameter(Constants.SEMI_MINOR);
-                final Unit<Length>      axisUnit  = ellipsoid.getAxisUnit();
-                /*
-                 * The two calls to getOptional(…) shall succeed before we write anything, in order to have a
-                 * "all or nothing" behavior as much as possible. Note that Ellipsoid.getSemi**Axis() have no
-                 * reason to fail, so we don't take precaution for them.
-                 */
-                final double a   = getValue(semiMajor, axisUnit);
-                final double b   = getValue(semiMinor, axisUnit);
-                final double tol = SI.METRE.getConverterTo(axisUnit).convert(ELLIPSOID_PRECISION);
-                if (ensureSet(semiMajor, a, ellipsoid.getSemiMajorAxis(), axisUnit, tol)) {
-                    mismatchedParam = semiMajor;
-                    mismatchedValue = a;
+                method = getOperationMethod(methodIdentifier);
+            } catch (NoSuchIdentifierException exception) {
+                if (methodIdentifier.equals(methodName)) {
+                    throw exception;
                 }
-                if (ensureSet(semiMinor, b, ellipsoid.getSemiMinorAxis(), axisUnit, tol)) {
-                    mismatchedParam = semiMinor;
-                    mismatchedValue = b;
-                }
-            } catch (IllegalArgumentException e) {
-                /*
-                 * Parameter not found, or is not numeric, or unit of measurement is not linear.
-                 * Those exceptions should never occur with map projections, but may happen for
-                 * some other operations like Molodenski¹.
-                 *
-                 * Do not touch to the parameters. We will see if createParameterizedTransform(…)
-                 * can do something about that. If it can not, createParameterizedTransform(…) is
-                 * the right place to throw the exception.
-                 *
-                 *  ¹ The actual Molodenski parameter names are "src_semi_major" and "src_semi_minor".
-                 *    But we do not try to set them because we have no way to set the corresponding
-                 *    "tgt_semi_major" and "tgt_semi_minor" parameters anyway.
-                 */
-                failure = e;
+                method = getOperationMethod(methodName);
+                Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
+                        DefaultMathTransformFactory.class, "createParameterizedTransform", exception);
             } catch (IllegalStateException e) {
                 failure = e;
             }
-            final boolean isIvfDefinitive;
-            if (mismatchedParam != null) {
-                final LogRecord record = Messages.getResources((Locale) null).getLogRecord(Level.WARNING,
-                        Messages.Keys.MismatchedEllipsoidAxisLength_3, ellipsoid.getName().getCode(),
-                        mismatchedParam.getDescriptor().getName().getCode(), mismatchedValue);
-                record.setLoggerName(Loggers.COORDINATE_OPERATION);
-                Logging.log(DefaultMathTransformFactory.class, "createBaseToDerived", record);
-                isIvfDefinitive = false;
-            } else {
-                isIvfDefinitive = ellipsoid.isIvfDefinitive();
+            if (!(method instanceof MathTransformProvider)) {
+                throw new NoSuchIdentifierException(Errors.format(          // For now, handle like an unknown operation.
+                        Errors.Keys.UnsupportedImplementation_1, Classes.getClass(method)), methodName);
             }
             /*
-             * Following is specific to Apache SIS. We use this non-standard API for allowing the
-             * NormalizedProjection class (our base class for all map projection implementations)
-             * to known that the ellipsoid definitive parameter is the inverse flattening factor
-             * instead than the semi-major axis length. It makes a small difference in the accuracy
-             * of the eccentricity parameter.
+             * 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 (isIvfDefinitive) try {
-                parameters.parameter(Constants.INVERSE_FLATTENING).setValue(ellipsoid.getInverseFlattening());
-            } catch (ParameterNotFoundException e) {
-                /*
-                 * Should never happen with Apache SIS implementation, but may happen if the given parameters come
-                 * from another implementation. We can safely abandon our attempt to set the inverse flattening value,
-                 * since it was redundant with semi-minor axis length.
-                 */
-                Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
-                        DefaultMathTransformFactory.class, "createBaseToDerived", e);
+            if (context != null) {
+                context.provider   = method;
+                context.parameters = parameters;
+                failure = context.setEllipsoids(method);
+                parameters = context.parameters;
+            }
+            /*
+             * Catch only exceptions which may be the result of improper parameter usage (e.g. a value out of range).
+             * Do not catch exception caused by programming errors (e.g. null pointer exception).
+             */
+            try {
+                transform = ((MathTransformProvider) method).createMathTransform(this, parameters);
+            } catch (RuntimeException exception) {  // (IllegalArgumentException | IllegalStateException) on the JDK7 branch.
+                throw new InvalidGeodeticParameterException(exception.getLocalizedMessage(), exception);
+            }
+            /*
+             * 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);
+            method = DefaultOperationMethod.redimension(method, transform.getSourceDimensions(),
+                                                                transform.getTargetDimensions());
+            if (context != null) {
+                transform = swapAndScaleAxes(transform, context);
             }
-        }
-        MathTransform baseToDerived;
-        try {
-            baseToDerived = createParameterizedTransform(parameters);
-            final OperationMethod method = lastMethod.get();
-            baseToDerived = createBaseToDerived(baseCRS.getCoordinateSystem(), baseToDerived, derivedCS);
-            lastMethod.set(method);
         } catch (FactoryException e) {
             if (failure != null) {
                 // e.addSuppressed(failure) on the JDK7 branch.
             }
             throw e;
+        } finally {
+            lastMethod.set(method);     // May be null in case of failure, which is intended.
+            if (context != null) {
+                context.provider = null;
+                // For now we conservatively reset the provider information to null. But if we choose to make
+                // that information public in a future SIS version, then we would remove this code.
+            }
         }
-        return baseToDerived;
+        return transform;
     }
 
     /**
-     * Creates a transform from a base to a derived CS using an existing parameterized transform.
-     * This convenience constructor {@linkplain #createConcatenatedTransform concatenates} the given parameterized
-     * transform with any other transform required for performing units changes and ordinates swapping.
+     * Given a transform between normalized spaces,
+     * create a transform taking in account axis directions and units of measurement.
+     * This method {@linkplain #createConcatenatedTransform concatenates} the given parameterized transform
+     * with any other transform required for performing units changes and ordinates swapping.
      *
      * <p>The given {@code parameterized} transform shall expect
      * {@linkplain org.apache.sis.referencing.cs.AxesConvention#NORMALIZED normalized} input coordinates and
@@ -606,23 +974,19 @@ public class DefaultMathTransformFactory
      * both of them with ({@linkplain org.opengis.referencing.cs.AxisDirection#EAST East},
      * {@linkplain org.opengis.referencing.cs.AxisDirection#NORTH North}) axis orientations.</div>
      *
-     * @param  baseCS        The source coordinate system.
-     * @param  parameterized A <cite>base to derived</cite> transform for normalized input and output coordinates.
-     * @param  derivedCS     The target coordinate system.
-     * @return The transform from {@code baseCS} to {@code derivedCS}, 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.
+     * @param  parameterized A transform for normalized input and output coordinates.
+     * @param  context Source and target coordinate systems in which the transform is going to be used.
+     * @return A transform taking in account unit conversions and axis swapping.
+     * @throws FactoryException if the object creation failed.
      *
      * @see org.apache.sis.referencing.cs.AxesConvention#NORMALIZED
      * @see org.apache.sis.referencing.operation.DefaultConversion#DefaultConversion(Map, OperationMethod, MathTransform, ParameterValueGroup)
      */
-    public MathTransform createBaseToDerived(final CoordinateSystem baseCS,
-            final MathTransform parameterized, final CoordinateSystem derivedCS)
-            throws FactoryException
-    {
-        ArgumentChecks.ensureNonNull("baseCS",        baseCS);
+    public MathTransform swapAndScaleAxes(final MathTransform parameterized, final Context context) throws FactoryException {
         ArgumentChecks.ensureNonNull("parameterized", parameterized);
-        ArgumentChecks.ensureNonNull("derivedCS",     derivedCS);
+        ArgumentChecks.ensureNonNull("context", context);
+        final CoordinateSystem sourceCS = context.getSourceCS();
+        final CoordinateSystem targetCS = context.getTargetCS();
         /*
          * Computes matrix for swapping axis and performing units conversion.
          * There is one matrix to apply before projection on (longitude,latitude)
@@ -631,10 +995,10 @@ public class DefaultMathTransformFactory
          */
         final Matrix swap1, swap3;
         try {
-            swap1 = CoordinateSystems.swapAndScaleAxes(baseCS, CoordinateSystems.replaceAxes(baseCS, AxesConvention.NORMALIZED));
-            swap3 = CoordinateSystems.swapAndScaleAxes(CoordinateSystems.replaceAxes(derivedCS, AxesConvention.NORMALIZED), derivedCS);
+            swap1 = (sourceCS != null) ? CoordinateSystems.swapAndScaleAxes(sourceCS, CoordinateSystems.replaceAxes(sourceCS, AxesConvention.NORMALIZED)) : null;
+            swap3 = (targetCS != null) ? CoordinateSystems.swapAndScaleAxes(CoordinateSystems.replaceAxes(targetCS, AxesConvention.NORMALIZED), targetCS) : null;
         } catch (IllegalArgumentException cause) {
-            throw new FactoryException(cause);
+            throw new InvalidGeodeticParameterException(cause.getLocalizedMessage(), cause);
         } catch (ConversionException cause) {
             throw new FactoryException(cause);
         }
@@ -644,10 +1008,18 @@ public class DefaultMathTransformFactory
          * For example the projection (step2) is usually two-dimensional while the source
          * coordinate system (step1) may be three-dimensional if it has a height.
          */
-        MathTransform step1 = createAffineTransform(swap1);
-        MathTransform step3 = createAffineTransform(swap3);
+        MathTransform step1 = (swap1 != null) ? createAffineTransform(swap1) : MathTransforms.identity(parameterized.getSourceDimensions());
+        MathTransform step3 = (swap3 != null) ? createAffineTransform(swap3) : MathTransforms.identity(parameterized.getTargetDimensions());
         MathTransform step2 = parameterized;
         /*
+         * Special case for the way EPSG handles reversal of axis direction. For now the "Vertical Offset" (EPSG:9616)
+         * method is the only one for which we found a need for special case. But if more special cases are added in a
+         * future SIS version, then we should replace the static method by a non-static one defined in AbstractProvider.
+         */
+        if (context.provider instanceof VerticalOffset) {
+            step2 = VerticalOffset.postCreate(step2, swap3);
+        }
+        /*
          * If the target coordinate system has a height, instructs the projection to pass
          * the height unchanged from the base CRS to the target CRS. After this block, the
          * dimensions of 'step2' and 'step3' should match.
@@ -683,63 +1055,65 @@ public class DefaultMathTransformFactory
     }
 
     /**
-     * Creates a transform from a group of parameters.
-     * The set of expected parameters varies for each operation.
-     * The easiest way to provide parameter values is to get an initially empty group for the desired
-     * operation by calling {@link #getDefaultParameters(String)}, then to fill the parameter values.
-     * Example:
-     *
-     * {@preformat java
-     *     ParameterValueGroup group = factory.getDefaultParameters("Transverse_Mercator");
-     *     group.parameter("semi_major").setValue(6378137.000);
-     *     group.parameter("semi_minor").setValue(6356752.314);
-     *     MathTransform mt = factory.createParameterizedTransform(group);
-     * }
+     * Creates a transform from a base CRS to a derived CS using the given parameters.
+     * If this method needs to set the values of {@code "semi_major"} and {@code "semi_minor"} parameters,
+     * then it sets those values directly on the given {@code parameters} instance – not on a clone – for
+     * allowing the caller to get back the complete parameter values.
+     * However this method only fills missing values, it never modify existing values.
      *
-     * @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.
+     * @param  baseCRS    The source coordinate reference system.
+     * @param  parameters The parameter values for the transform.
+     * @param  derivedCS  The target coordinate system.
+     * @return The parameterized transform from {@code baseCRS} to {@code derivedCS},
+     *         including unit conversions and axis swapping.
+     * @throws NoSuchIdentifierException if there is no transform registered for the coordinate operation method.
      * @throws FactoryException if the object creation failed. This exception is thrown
      *         if some required parameter has not been supplied, or has illegal value.
      *
-     * @see #getDefaultParameters(String)
-     * @see #getAvailableMethods(Class)
-     * @see #getLastMethodUsed()
+     * @deprecated Replaced by {@link #createParameterizedTransform(ParameterValueGroup, Context)}.
      */
     @Override
-    public MathTransform createParameterizedTransform(final ParameterValueGroup parameters)
+    @Deprecated
+    public MathTransform createBaseToDerived(final CoordinateReferenceSystem baseCRS,
+            final ParameterValueGroup parameters, final CoordinateSystem derivedCS)
             throws NoSuchIdentifierException, FactoryException
     {
+        ArgumentChecks.ensureNonNull("baseCRS",    baseCRS);
         ArgumentChecks.ensureNonNull("parameters", parameters);
-        final String methodName = parameters.getDescriptor().getName().getCode();
-        OperationMethod method = null;
-        try {
-            method = getOperationMethod(methodName);
-            if (!(method instanceof MathTransformProvider)) {
-                throw new NoSuchIdentifierException(Errors.format( // For now, handle like an unknown operation.
-                        Errors.Keys.UnsupportedImplementation_1, Classes.getClass(method)), methodName);
-            }
-            MathTransform transform;
-            try {
-                transform  = ((MathTransformProvider) method).createMathTransform(this, parameters);
-            } catch (IllegalArgumentException exception) {
-                /*
-                 * Catch only exceptions which may be the result of improper parameter
-                 * usage (e.g. a value out of range). Do not catch exception caused by
-                 * programming errors (e.g. null pointer exception).
-                 */
-                throw new FactoryException(exception);
-            } catch (IllegalStateException exception) {
-                throw new FactoryException(exception);
-            }
-            transform = unique(transform);
-            method = DefaultOperationMethod.redimension(method,
-                    transform.getSourceDimensions(), transform.getTargetDimensions());
-            return transform;
-        } finally {
-            lastMethod.set(method); // May be null in case of failure, which is intended.
-        }
+        ArgumentChecks.ensureNonNull("derivedCS",  derivedCS);
+        final Context context = new Context();
+        context.setSource(baseCRS);
+        context.setTarget(derivedCS);
+        return createParameterizedTransform(parameters, context);
+    }
+
+    /**
+     * Creates a transform from a base to a derived CS using an existing parameterized transform.
+     * The given {@code parameterized} transform shall expect
+     * {@linkplain org.apache.sis.referencing.cs.AxesConvention#NORMALIZED normalized} input coordinates and
+     * produce normalized output coordinates.
+     *
+     * @param  baseCS        The source coordinate system.
+     * @param  parameterized A <cite>base to derived</cite> transform for normalized input and output coordinates.
+     * @param  derivedCS     The target coordinate system.
+     * @return The transform from {@code baseCS} to {@code derivedCS}, 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.
+     *
+     * @deprecated Replaced by {@link #swapAndScaleAxes(MathTransform, Context)}.
+     */
+    @Deprecated
+    public MathTransform createBaseToDerived(final CoordinateSystem baseCS,
+            final MathTransform parameterized, final CoordinateSystem derivedCS)
+            throws FactoryException
+    {
+        ArgumentChecks.ensureNonNull("baseCS",        baseCS);
+        ArgumentChecks.ensureNonNull("parameterized", parameterized);
+        ArgumentChecks.ensureNonNull("derivedCS",     derivedCS);
+        final Context context = new Context();
+        context.setSource(baseCS);
+        context.setTarget(derivedCS);
+        return swapAndScaleAxes(parameterized, context);
     }
 
     /**
@@ -788,7 +1162,7 @@ public class DefaultMathTransformFactory
         try {
             tr = ConcatenatedTransform.create(tr1, tr2, this);
         } catch (IllegalArgumentException exception) {
-            throw new FactoryException(exception);
+            throw new InvalidGeodeticParameterException(exception.getLocalizedMessage(), exception);
         }
         assert MathTransforms.isValid(MathTransforms.getSteps(tr)) : tr;
         return unique(tr);
@@ -828,22 +1202,22 @@ public class DefaultMathTransformFactory
         try {
             tr = PassThroughTransform.create(firstAffectedOrdinate, subTransform, numTrailingOrdinates);
         } catch (IllegalArgumentException exception) {
-            throw new FactoryException(exception);
+            throw new InvalidGeodeticParameterException(exception.getLocalizedMessage(), exception);
         }
         return unique(tr);
     }
 
     /**
-     * Creates a math transform object from a XML string. The default implementation
-     * always throws an exception, since this constructor is not yet implemented.
+     * There is no XML format for math transforms.
      *
      * @param  xml Math transform encoded in XML format.
      * @throws FactoryException if the object creation failed.
      */
     @Override
+    @Deprecated
     public MathTransform createFromXML(String xml) throws FactoryException {
         lastMethod.remove();
-        throw new FactoryException("Not yet implemented.");
+        throw new FactoryException(Errors.format(Errors.Keys.UnsupportedOperation_1, "createFromXML"));
     }
 
     /**
@@ -893,13 +1267,11 @@ public class DefaultMathTransformFactory
      * in the currently running thread. Returns {@code null} if not applicable.
      *
      * <p>Invoking {@code getLastMethodUsed()} can be useful after a call to
-     * {@link #createParameterizedTransform createParameterizedTransform(…)}, or after a call to another
-     * constructor that delegates its work to {@code createParameterizedTransform(…)}, for example
-     * {@link #createBaseToDerived createBaseToDerived(…)}.</p>
+     * {@link #createParameterizedTransform createParameterizedTransform(…)}.</p>
      *
      * @return The last method used by a {@code create(…)} constructor, or {@code null} if unknown of unsupported.
      *
-     * @see #createParameterizedTransform(ParameterValueGroup)
+     * @see #createParameterizedTransform(ParameterValueGroup, Context)
      */
     @Override
     public OperationMethod getLastMethodUsed() {

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -30,6 +30,7 @@ import org.opengis.parameter.ParameterVa
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
@@ -302,6 +303,10 @@ public class EllipsoidToCentricTransform
 
     /**
      * Restores transient fields after deserialization.
+     *
+     * @param  in The input stream from which to deserialize the transform.
+     * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
+     * @throws ClassNotFoundException if the class serialized on the stream is not on the classpath.
      */
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
@@ -347,6 +352,34 @@ public class EllipsoidToCentricTransform
     }
 
     /**
+     * Creates a transform from geographic to Cartesian geocentric coordinates (convenience method).
+     * Invoking this method is equivalent to the following:
+     *
+     * {@preformat java
+     *     createGeodeticConversion(factory,
+     *             ellipsoid.getSemiMajorAxis(),
+     *             ellipsoid.getSemiMinorAxis(),
+     *             ellipsoid.getAxisUnit(),
+     *             withHeight, TargetType.CARTESIAN);
+     * }
+     *
+     * The target type is assumed Cartesian because this is the most frequently used target.
+     *
+     * @param factory    The factory to use for creating and concatenating the affine transforms.
+     * @param ellipsoid  The semi-major and semi-minor axis lengths with their unit of measurement.
+     * @param withHeight {@code true} if source geographic coordinates include an ellipsoidal height
+     *                   (i.e. are 3-D), or {@code false} if they are only 2-D.
+     * @return The conversion from geographic to Cartesian geocentric coordinates.
+     * @throws FactoryException if an error occurred while creating a transform.
+     */
+    public static MathTransform createGeodeticConversion(final MathTransformFactory factory,
+            final Ellipsoid ellipsoid, final boolean withHeight) throws FactoryException
+    {
+        return createGeodeticConversion(factory, ellipsoid.getSemiMajorAxis(), ellipsoid.getSemiMinorAxis(),
+                ellipsoid.getAxisUnit(), withHeight, TargetType.CARTESIAN);
+    }
+
+    /**
      * Returns the parameters used for creating the complete conversion. Those parameters describe a sequence
      * of <cite>normalize</cite> → {@code this} → <cite>denormalize</cite> transforms, <strong>not</strong>
      * including {@linkplain org.apache.sis.referencing.cs.CoordinateSystems#swapAndScaleAxes axis swapping}.
@@ -436,6 +469,11 @@ public class EllipsoidToCentricTransform
      * This method relaxes a little bit the {@code MathTransform} contract by accepting two- or three-dimensional
      * points even if the number of dimensions does not match the {@link #getSourceDimensions()} value.
      *
+     * <div class="note"><b>Rational:</b>
+     * that flexibility on the number of dimensions is required for calculation of {@linkplain #inverse() inverse}
+     * transform derivative, because that calculation needs to inverse a square matrix with all terms in it before
+     * to drop the last row in the two-dimensional case.</div>
+     *
      * @param  point The coordinate point where to evaluate the derivative.
      * @return The derivative at the specified point (never {@code null}).
      * @throws TransformException if the derivative can not be evaluated at the specified point.
@@ -695,7 +733,9 @@ next:   while (--numPts >= 0) {
      */
     @Override
     protected int computeHashCode() {
-        return super.computeHashCode() + Numerics.hashCode(Double.doubleToLongBits(axisRatio));
+        int code = super.computeHashCode() + Numerics.hashCode(Double.doubleToLongBits(axisRatio));
+        if (withHeight) code += 37;
+        return code;
     }
 
     /**
@@ -712,6 +752,7 @@ next:   while (--numPts >= 0) {
         if (super.equals(object, mode)) {
             final EllipsoidToCentricTransform that = (EllipsoidToCentricTransform) object;
             return (withHeight == that.withHeight) && Numerics.equals(axisRatio, that.axisRatio);
+            // No need to compare the contextual parameters since this is done by super-class.
         }
         return false;
     }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/IdentityTransform.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/IdentityTransform.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/IdentityTransform.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/IdentityTransform.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -18,7 +18,6 @@ package org.apache.sis.referencing.opera
 
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.operation.Matrix;
-import org.opengis.referencing.operation.MathTransform;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.geometry.GeneralDirectPosition;
 import org.apache.sis.referencing.operation.matrix.Matrices;
@@ -228,7 +227,7 @@ final class IdentityTransform extends Ab
      * Returns the inverse transform of this object, which is this transform itself
      */
     @Override
-    public MathTransform inverse() {
+    public LinearTransform inverse() {
         return this;
     }
 

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -18,6 +18,8 @@ package org.apache.sis.referencing.opera
 
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
 
 
 /**
@@ -61,7 +63,7 @@ import org.opengis.referencing.operation
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.6
+ * @version 0.7
  * @module
  *
  * @see org.apache.sis.referencing.operation.transform.MathTransforms#linear(Matrix)
@@ -93,4 +95,42 @@ public interface LinearTransform extends
      * @see MathTransforms#getMatrix(MathTransform)
      */
     Matrix getMatrix();
+
+    /**
+     * Transforms an array of relative distance vectors.
+     * Distance vectors are transformed without applying the translation components.
+     * The supplied array of distance values will contain packed values.
+     *
+     * <div class="note"><b>Example:</b> if the source dimension is 3, then the values will be packed in this order:
+     * (<var>Δx₀</var>,<var>Δy₀</var>,<var>Δz₀</var>,
+     *  <var>Δx₁</var>,<var>Δy₁</var>,<var>Δz₁</var> …).
+     * </div>
+     *
+     * @param  srcPts The array containing the source vectors.
+     * @param  srcOff The offset to the first vector to be transformed in the source array.
+     * @param  dstPts The array into which the transformed vectors are returned. Can be the same than {@code srcPts}.
+     * @param  dstOff The offset to the location of the first transformed vector that is stored in the destination array.
+     * @param  numPts The number of vector objects to be transformed.
+     * @throws TransformException if a vector can not be transformed.
+     *
+     * @see java.awt.geom.AffineTransform#deltaTransform(double[], int, double[], int, int)
+     *
+     * @since 0.7
+     */
+    void deltaTransform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) throws TransformException;
+
+    /**
+     * Returns the inverse transform of this object, which shall also be linear.
+     * The target of the inverse transform is the source of the original.
+     * The source of the inverse transform is the target of the original.
+     *
+     * @return The inverse transform.
+     * @throws NoninvertibleTransformException if the transform can not be inverted.
+     *
+     * @see java.awt.geom.AffineTransform#createInverse()
+     *
+     * @since 0.7
+     */
+    @Override
+    LinearTransform inverse() throws NoninvertibleTransformException;
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java?rev=1724531&r1=1724530&r2=1724531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java [UTF-8] Wed Jan 13 23:59:38 2016
@@ -142,7 +142,7 @@ class LinearTransform1D extends Abstract
      * Creates the inverse transform of this object.
      */
     @Override
-    public MathTransform1D inverse() throws NoninvertibleTransformException {
+    public LinearTransform1D inverse() throws NoninvertibleTransformException {
         if (inverse == null) {
             /*
              * Note: we do not perform the following optimization, because MathTransforms.linear(…)
@@ -158,10 +158,10 @@ class LinearTransform1D extends Abstract
                 inverse.inverse = this;
                 this.inverse = inverse;
             } else {
-                inverse = super.inverse();
+                inverse = super.inverse();      // Throws NoninvertibleTransformException
             }
         }
-        return inverse;
+        return (LinearTransform1D) inverse;
     }
 
     /**
@@ -239,7 +239,7 @@ class LinearTransform1D extends Abstract
     public void transform(final double[] srcPts, int srcOff,
                           final double[] dstPts, int dstOff, int numPts)
     {
-        if (srcPts!=dstPts || srcOff>=dstOff) {
+        if (srcPts != dstPts || srcOff >= dstOff) {
             while (--numPts >= 0) {
                 dstPts[dstOff++] = offset + scale * srcPts[srcOff++];
             }
@@ -261,7 +261,7 @@ class LinearTransform1D extends Abstract
     public void transform(final float[] srcPts, int srcOff,
                           final float[] dstPts, int dstOff, int numPts)
     {
-        if (srcPts!=dstPts || srcOff>=dstOff) {
+        if (srcPts != dstPts || srcOff >= dstOff) {
             while (--numPts >= 0) {
                 dstPts[dstOff++] = (float) (offset + scale * srcPts[srcOff++]);
             }
@@ -301,6 +301,29 @@ class LinearTransform1D extends Abstract
         }
     }
 
+    /**
+     * Transforms many distance vectors in a list of ordinal values.
+     * The default implementation computes the values from the {@link #scale} coefficient only.
+     *
+     * @since 0.7
+     */
+    @Override
+    public void deltaTransform(final double[] srcPts, int srcOff,
+                               final double[] dstPts, int dstOff, int numPts)
+    {
+        if (srcPts != dstPts || srcOff >= dstOff) {
+            while (--numPts >= 0) {
+                dstPts[dstOff++] = scale * srcPts[srcOff++];
+            }
+        } else {
+            srcOff += numPts;
+            dstOff += numPts;
+            while (--numPts >= 0) {
+                dstPts[--dstOff] = scale * srcPts[--srcOff];
+            }
+        }
+    }
+
     /**
      * {@inheritDoc}
      */



Mime
View raw message