Author: desruisseaux
Date: Fri Mar 25 18:23:14 2016
New Revision: 1736638

URL: http://svn.apache.org/viewvc?rev=1736638&view=rev
Log:
Replaced CoordinateOperationInference.createOperationStep(GeocentricCRS, 
GeocentricCRS) by a version working on GeodeticCRS, thus including 
GeographicCRS.
Previously (in Geotk) we had two separated methods for the Geocentric and 
Geographic cases. But that separation does not exist in ISO 19111:2007 standard,
and indeed merging those two methods in a single one gives something both 
simpler and more powerful (better handling of change of coordinate system type).
This work required changes in GeocentricAffine and other internal classes for 
creating more appropriate operation depending on the coordinate system type.

Added:
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
   (with props)
Modified:
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
 [UTF-8] Fri Mar 25 18:23:14 2016
@@ -24,6 +24,10 @@ import org.opengis.util.FactoryException
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.cs.CartesianCS;
+import org.opengis.referencing.cs.EllipsoidalCS;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.Transformation;
@@ -67,6 +71,14 @@ public abstract class GeocentricAffine e
     private static final long serialVersionUID = 8291967302538661639L;
 
     /**
+     * The tolerance factor for comparing the {@link BursaWolfParameters} 
values.
+     * We use a tolerance of 1E-6 ({@value Formulas#LINEAR_TOLERANCE} / 10000) 
based on the knowledge
+     * that the translation terms are in metres and the rotation terms have 
the some order of magnitude.
+     * Actually we could use a value of zero, but we add a small tolerance for 
rounding errors.
+     */
+    private static final double BURSAWOLF_TOLERANCE = 
Formulas.LINEAR_TOLERANCE / 10000;
+
+    /**
      * The operation parameter descriptor for the <cite>X-axis 
translation</cite>
      * ({@linkplain BursaWolfParameters#tX tX}) parameter value. Valid values 
range
      * from negative to positive infinity. Units are {@linkplain SI#METRE 
metres}.
@@ -200,6 +212,103 @@ public abstract class GeocentricAffine e
     }
 
     /**
+     * Creates parameter values for a Molodensky, Geocentric Translation or 
Position Vector transformation.
+     *
+     * @param  descriptor     The {@code PARAMETERS} constant of the subclass 
describing the operation to create.
+     * @param  parameters     Bursa-Wolf parameters from which to get the 
values.
+     * @param  isTranslation  {@code true} if the operation contains only 
translation terms.
+     * @return The operation parameters with their values initialized.
+     */
+    private static Parameters createParameters(final ParameterDescriptorGroup 
descriptor,
+            final BursaWolfParameters parameters, final boolean isTranslation)
+    {
+        final Parameters values = 
Parameters.castOrWrap(descriptor.createValue());
+        values.getOrCreate(TX).setValue(parameters.tX);
+        values.getOrCreate(TY).setValue(parameters.tY);
+        values.getOrCreate(TZ).setValue(parameters.tZ);
+        if (!isTranslation) {
+            values.getOrCreate(RX).setValue(parameters.rX);
+            values.getOrCreate(RY).setValue(parameters.rY);
+            values.getOrCreate(RZ).setValue(parameters.rZ);
+            values.getOrCreate(DS).setValue(parameters.dS);
+        }
+        return values;
+    }
+
+    /**
+     * Returns the parameters for creating a datum shift operation.
+     * The operation method will be one of the {@code GeocentricAffine} 
subclasses.
+     * If no single operation method can be used, then this method returns 
{@code null}.
+     *
+     * <p>This method does <strong>not</strong> change the coordinate system 
type.
+     * The source and target coordinate systems can be both {@code 
EllipsoidalCS} or both {@code CartesianCS}.
+     * Any other type or mix of types (e.g. a {@code EllipsoidalCS} source and 
{@code CartesianCS} target)
+     * will cause this method to return {@code null}. In such case, it is 
caller's responsibility to apply
+     * the datum shift itself in Cartesian geocentric coordinates.</p>
+     *
+     * @param sourceCS       The source coordinate system. Only the type and 
number of dimensions is checked.
+     * @param targetCS       The target coordinate system. Only the type and 
number of dimensions is checked.
+     * @param datumShift     The datum shift as a matrix.
+     * @param useMolodensky  {@code true} for allowing the use of Molodensky 
approximation, or {@code false}
+     *                       for using the transformation in geocentric space 
(which should be more accurate).
+     * @return The parameter values, or {@code null} if no single operation 
method can be found.
+     */
+    public static ParameterValueGroup createParameters(final CoordinateSystem 
sourceCS,
+            final CoordinateSystem targetCS, final Matrix datumShift, boolean 
useMolodensky)
+    {
+        final boolean isEllipsoidal = (sourceCS instanceof EllipsoidalCS);
+        if (!(isEllipsoidal ? targetCS instanceof EllipsoidalCS
+                            : targetCS instanceof CartesianCS && sourceCS 
instanceof CartesianCS))
+        {
+            return null;        // Coordinate systems are not two 
EllipsoidalCS or two CartesianCS.
+        }
+        @SuppressWarnings("null")
+        int dimension  = sourceCS.getDimension();
+        if (dimension != targetCS.getDimension()) {
+            dimension  = 0;                             // Sentinal value for 
mismatched dimensions.
+        }
+        /*
+         * Try to convert the matrix into (tX, tY, tZ, rX, rY, rZ, dS) 
parameters.
+         * The matrix may not be convertible, in which case we will let the 
callers
+         * uses the matrix directly in Cartesian geocentric coordinates.
+         */
+        final BursaWolfParameters parameters = new BursaWolfParameters(null, 
null);
+        try {
+            parameters.setPositionVectorTransformation(datumShift, 
BURSAWOLF_TOLERANCE);
+        } catch (IllegalArgumentException e) {
+            log(Loggers.COORDINATE_OPERATION, "createParameters", e);
+            return null;
+        }
+        final boolean isTranslation = parameters.isTranslation();
+        final ParameterDescriptorGroup descriptor;
+        /*
+         * Following "if" blocks are ordered from more accurate to less 
accurate datum shift method
+         * supported by GeocentricAffine subclasses.
+         */
+        if (!isEllipsoidal) {
+            useMolodensky = false;
+            descriptor = isTranslation ? GeocentricTranslation.PARAMETERS
+                                       : PositionVector7Param .PARAMETERS;
+        } else {
+            if (!isTranslation) {
+                useMolodensky = false;
+                descriptor = (dimension >= 3) ? 
PositionVector7Param3D.PARAMETERS
+                                              : 
PositionVector7Param2D.PARAMETERS;
+            } else if (!useMolodensky) {
+                descriptor = (dimension >= 3) ? 
GeocentricTranslation3D.PARAMETERS
+                                              : 
GeocentricTranslation2D.PARAMETERS;
+            } else {
+                descriptor = Molodensky.PARAMETERS;
+            }
+        }
+        final Parameters values = createParameters(descriptor, parameters, 
isTranslation);
+        if (useMolodensky && dimension != 0) {
+            values.getOrCreate(Molodensky.DIMENSION).setValue(dimension);
+        }
+        return values;
+    }
+
+    /**
      * Given a transformation chain, conditionally replaces the affine 
transform elements by an alternative object
      * showing the Bursa-Wolf parameters. The replacement is applied if and 
only if the affine transform is a scale,
      * translation or rotation in the geocentric domain.
@@ -211,7 +320,7 @@ public abstract class GeocentricAffine e
      *
      * @param transforms The full chain of concatenated transforms.
      */
-    public static void asDatumShift(final List<Object> transforms) throws 
IllegalArgumentException {
+    public static void asDatumShift(final List<Object> transforms) {
         for (int i=transforms.size() - 2; --i >= 0;) {
             if (isOperation(GeographicToGeocentric.NAME, transforms.get(i)) &&
                 isOperation(GeocentricToGeographic.NAME, transforms.get(i+2)))
@@ -220,31 +329,18 @@ public abstract class GeocentricAffine e
                 if (step instanceof LinearTransform) {
                     final BursaWolfParameters parameters = new 
BursaWolfParameters(null, null);
                     try {
-                        /*
-                         * We use a 0.01 metre tolerance 
(Formulas.LINEAR_TOLERANCE) based on the knowledge that the
-                         * translation terms are in metres and the rotation 
terms have the some order of magnitude.
-                         */
-                        
parameters.setPositionVectorTransformation(((LinearTransform) 
step).getMatrix(), Formulas.LINEAR_TOLERANCE);
+                        
parameters.setPositionVectorTransformation(((LinearTransform) 
step).getMatrix(), BURSAWOLF_TOLERANCE);
                     } catch (IllegalArgumentException e) {
                         /*
                          * Should not occur, except sometime on inverse 
transform of relatively complex datum shifts
                          * (more than just translation terms). We can fallback 
on formatting the full matrix.
                          */
-                        
Logging.recoverableException(Logging.getLogger(Loggers.WKT), 
GeocentricAffine.class, "asDatumShift", e);
+                        log(Loggers.WKT, "asDatumShift", e);
                         continue;
                     }
                     final boolean isTranslation = parameters.isTranslation();
-                    final Parameters values = Parameters.castOrWrap(
-                            (isTranslation ? GeocentricTranslation.PARAMETERS 
: PositionVector7Param.PARAMETERS).createValue());
-                    values.getOrCreate(TX).setValue(parameters.tX);
-                    values.getOrCreate(TY).setValue(parameters.tY);
-                    values.getOrCreate(TZ).setValue(parameters.tZ);
-                    if (!isTranslation) {
-                        values.getOrCreate(RX).setValue(parameters.rX);
-                        values.getOrCreate(RY).setValue(parameters.rY);
-                        values.getOrCreate(RZ).setValue(parameters.rZ);
-                        values.getOrCreate(DS).setValue(parameters.dS);
-                    }
+                    final Parameters values = createParameters(isTranslation ? 
GeocentricTranslation.PARAMETERS
+                                            : PositionVector7Param.PARAMETERS, 
parameters, isTranslation);
                     transforms.set(i+1, new FormattableObject() {
                         @Override protected String formatTo(final Formatter 
formatter) {
                             WKTUtilities.appendParamMT(values, formatter);
@@ -263,4 +359,11 @@ public abstract class GeocentricAffine e
         return (actual instanceof Parameterized) &&
                IdentifiedObjects.isHeuristicMatchForName(((Parameterized) 
actual).getParameterDescriptors(), expected);
     }
+
+    /**
+     * Logs a warning about a failure to compute the Bursa-Wolf parameters.
+     */
+    private static void log(final String logger, final String method, final 
Exception e) {
+        Logging.recoverableException(Logging.getLogger(logger), 
GeocentricAffine.class, method, e);
+    }
 }

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java
 [UTF-8] Fri Mar 25 18:23:14 2016
@@ -40,7 +40,7 @@ public final class GeocentricTranslation
     /**
      * The group of all parameters expected by this coordinate operation.
      */
-    private static final ParameterDescriptorGroup PARAMETERS;
+    static final ParameterDescriptorGroup PARAMETERS;
     static {
         PARAMETERS = builder()
                 .addIdentifier("1035")

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
 [UTF-8] Fri Mar 25 18:23:14 2016
@@ -679,7 +679,7 @@ public class TensorParameters<E> impleme
                 if (++indices[j] < actualSize[j]) {
                     break;
                 }
-                indices[j] = 0; // We have done a full turn at that dimension. 
Will increment next dimension.
+                indices[j] = 0;         // We have done a full turn at that 
dimension. Will increment next dimension.
             }
         }
         return parameters;
@@ -737,6 +737,8 @@ public class TensorParameters<E> impleme
      * @param  properties The properties to be given to the identified object.
      * @param  matrix The matrix to copy in the new parameter group.
      * @return A new parameter group initialized to the given matrix.
+     *
+     * @see #toMatrix(ParameterValueGroup)
      */
     public ParameterValueGroup createValueGroup(final Map<String,?> 
properties, final Matrix matrix) {
         if (rank() != 2) {
@@ -755,6 +757,8 @@ public class TensorParameters<E> impleme
      * @param  parameters The group of parameters.
      * @return A matrix constructed from the specified group of parameters.
      * @throws InvalidParameterNameException if a parameter name was not 
recognized.
+     *
+     * @see #createValueGroup(Map, Matrix)
      */
     public Matrix toMatrix(final ParameterValueGroup parameters) throws 
InvalidParameterNameException {
         if (rank() != 2) {
@@ -762,7 +766,7 @@ public class TensorParameters<E> impleme
         }
         ArgumentChecks.ensureNonNull("parameters", parameters);
         if (parameters instanceof TensorValues) {
-            return ((TensorValues) parameters).toMatrix(); // More efficient 
implementation
+            return ((TensorValues) parameters).toMatrix();              // 
More efficient implementation
         }
         // Fallback on the general case (others implementations)
         final ParameterValue<?> numRow = 
parameters.parameter(dimensions[0].getName().getCode());

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
 [UTF-8] Fri Mar 25 18:23:14 2016
@@ -534,10 +534,14 @@ public class BursaWolfParameters extends
         }
         /*
          * Translation terms, taken "as-is".
+         * If the matrix contains only translation terms (which is often the 
case), we are done.
          */
         tX = matrix.getElement(0,3);
         tY = matrix.getElement(1,3);
         tZ = matrix.getElement(2,3);
+        if (Matrices.isTranslation(matrix)) {   // Optimization for a common 
case.
+            return;
+        }
         /*
          * Scale factor: take the average of elements on the diagonal. All 
those
          * elements should have the same value, but we tolerate slight 
deviation

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
 [UTF-8] Fri Mar 25 18:23:14 2016
@@ -225,14 +225,14 @@ class AbstractSingleOperation extends Ab
          * ignoring null java.lang.Integer instances.  We do not specify 
whether the method
          * dimensions should include the interpolation dimensions or not, so 
we accept both.
          */
-        int isTarget = 0;   // 0 == false: the wrong dimension is the source 
one.
+        int isTarget = 0;               // 0 == false: the wrong dimension is 
the source one.
         if (expected == null || (actual == expected) || (actual == expected + 
interpDim)) {
             actual = transform.getTargetDimensions();
             expected = method.getTargetDimensions();
             if (expected == null || (actual == expected) || (actual == 
expected + interpDim)) {
                 return;
             }
-            isTarget = 1;   // 1 == true: the wrong dimension is the target 
one.
+            isTarget = 1;               // 1 == true: the wrong dimension is 
the target one.
         }
         /*
          * At least one dimension does not match.  In principle this is an 
error, but we make an exception for the

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java
 [UTF-8] Fri Mar 25 18:23:14 2016
@@ -20,7 +20,6 @@ import java.util.Map;
 import java.util.HashMap;
 import java.util.Collections;
 import javax.measure.unit.Unit;
-import javax.measure.unit.NonSI;
 import javax.measure.quantity.Duration;
 import javax.measure.converter.ConversionException;
 import org.opengis.util.FactoryException;
@@ -33,24 +32,27 @@ import org.opengis.metadata.Identifier;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.quality.PositionalAccuracy;
+import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.apache.sis.internal.metadata.ReferencingServices;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.referencing.PositionalAccuracyConstant;
+import org.apache.sis.internal.referencing.provider.GeocentricAffine;
 import org.apache.sis.internal.referencing.provider.Affine;
 import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.internal.util.Constants;
 import org.apache.sis.measure.Units;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.parameter.Parameterized;
+import org.apache.sis.parameter.TensorParameters;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.NamedIdentifier;
-import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.datum.BursaWolfParameters;
 import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
-import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
@@ -61,7 +63,6 @@ import org.apache.sis.util.resources.Err
 import org.apache.sis.util.resources.Vocabulary;
 
 import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
-import static 
org.apache.sis.internal.referencing.ReferencingUtilities.getGreenwichLongitude;
 
 // Branch-dependent imports
 import java.util.Objects;
@@ -89,6 +90,17 @@ import java.util.Objects;
  */
 public class CoordinateOperationInference {
     /**
+     * The accuracy threshold (in metres) for allowing the use of Molodensky 
approximation instead than the
+     * Geocentric Translation method. The accuracy of datum shifts with 
Molodensky approximation is about 5
+     * or 10 metres. However for this constant, we are not interested in 
absolute accuracy but rather in the
+     * difference between Molodensky and Geocentric Translation methods, which 
is much lower. We nevertheless
+     * use a relatively high threshold as a conservative approach.
+     *
+     * @see #desiredAccuracy
+     */
+    private static final double MOLODENSKY_ACCURACY = 5;
+
+    /**
      * The identifier for an identity operation.
      */
     private static final Identifier IDENTITY = 
createIdentifier(Vocabulary.Keys.Identity);
@@ -153,6 +165,8 @@ public class CoordinateOperationInferenc
 
     /**
      * The desired accuracy in metres, or 0 for the best accuracy available.
+     *
+     * @see #MOLODENSKY_ACCURACY
      */
     private double desiredAccuracy;
 
@@ -177,23 +191,6 @@ public class CoordinateOperationInferenc
     }
 
     /**
-     * The operation to use by {@link 
#createTransformationStep(GeographicCRS,GeographicCRS)}
-     * for datum shift. This string can have one of the following values, from 
most accurate
-     * to most approximative operations:
-     *
-     * <ul>
-     *   <li>{@code null} for performing datum shifts in geocentric 
coordinates.</li>
-     *   <li>{@code "Molodensky"} for the Molodensky transformation.</li>
-     *   <li>{@code "Abridged Molodensky"} for the abridged Molodensky 
transformation.</li>
-     * </ul>
-     *
-     * @todo Value will need to be determined according the desired accuracy.
-     */
-    private String getMolodenskyMethod() {
-        return "Molodensky";
-    }
-
-    /**
      * If the domain of interest was not set, defines it to the domain of 
validity of the given CRS.
      */
     private void updateDomainOfInterest(final CoordinateReferenceSystem 
sourceCRS,
@@ -265,40 +262,23 @@ public class CoordinateOperationInferenc
         }
         
////////////////////////////////////////////////////////////////////////////////
         ////                                                                   
     ////
-        ////           Geographic  →  Geographic, Projected or Geocentric      
     ////
+        ////            Geodetic  →  Geocetric, Geographic or Projected        
     ////
         ////                                                                   
     ////
         
////////////////////////////////////////////////////////////////////////////////
-        if (sourceCRS instanceof GeographicCRS) {
-            final GeographicCRS source = (GeographicCRS) sourceCRS;
-            if (targetCRS instanceof GeographicCRS) {
-//              return createOperationStep(source, (GeographicCRS) targetCRS);
+        if (sourceCRS instanceof GeodeticCRS) {
+            final GeodeticCRS source = (GeodeticCRS) sourceCRS;
+            if (targetCRS instanceof GeodeticCRS) {
+                return createOperationStep(source, (GeodeticCRS) targetCRS);
             }
             if (targetCRS instanceof ProjectedCRS) {
 //              return createOperationStep(source, (ProjectedCRS) targetCRS);
             }
-            if (targetCRS instanceof GeocentricCRS) {
-//              return createOperationStep(source, (GeocentricCRS) targetCRS);
-            }
             if (targetCRS instanceof VerticalCRS) {
 //              return createOperationStep(source, (VerticalCRS) targetCRS);
             }
         }
         
////////////////////////////////////////////////////////////////////////////////
         ////                                                                   
     ////
-        ////                Geocentric  →  Geocentric or Geographic            
     ////
-        ////                                                                   
     ////
-        
////////////////////////////////////////////////////////////////////////////////
-        if (sourceCRS instanceof GeocentricCRS) {
-            final GeocentricCRS source = (GeocentricCRS) sourceCRS;
-            if (targetCRS instanceof GeocentricCRS) {
-                return createOperationStep(source, (GeocentricCRS) targetCRS);
-            }
-            if (targetCRS instanceof GeographicCRS) {
-//              return createOperationStep(source, (GeographicCRS) targetCRS);
-            }
-        }
-        
////////////////////////////////////////////////////////////////////////////////
-        ////                                                                   
     ////
         ////                         Vertical  →  Vertical                     
     ////
         ////                                                                   
     ////
         
////////////////////////////////////////////////////////////////////////////////
@@ -323,111 +303,128 @@ public class CoordinateOperationInferenc
     }
 
     /**
-     * Creates an operation between two geocentric coordinate reference 
systems.
-     * The default implementation can adjust for axis order, orientation and 
units of measurement.
-     * If the datums are not the equal but {@linkplain 
DefaultGeodeticDatum#getBursaWolfParameters()
-     * Bursa-Wolf parameters exists} between the two datum in the area of 
interest, then this method
-     * will also perform a datum shift.
+     * Creates an operation between two geodetic (geographic or geocentric) 
coordinate reference systems.
+     * The default implementation can:
+     *
+     * <ul>
+     *   <li>adjust axis order and orientation, for example converting from 
(<cite>North</cite>, <cite>West</cite>)
+     *       axes to (<cite>East</cite>, <cite>North</cite>) axes,</li>
+     *   <li>apply units conversion if needed,</li>
+     *   <li>perform longitude rotation if needed,</li>
+     *   <li>perform datum shift if {@linkplain BursaWolfParameters Bursa-Wolf 
parameters} are available
+     *       for the area of interest.</li>
+     * </ul>
      *
      * @param  sourceCRS  input coordinate reference system.
      * @param  targetCRS  output coordinate reference system.
      * @return a coordinate operation from {@code sourceCRS} to {@code 
targetCRS}.
      * @throws FactoryException if the operation can not be constructed.
      */
-    protected CoordinateOperation createOperationStep(final GeocentricCRS 
sourceCRS,
-                                                      final GeocentricCRS 
targetCRS)
+    protected CoordinateOperation createOperationStep(final GeodeticCRS 
sourceCRS,
+                                                      final GeodeticCRS 
targetCRS)
             throws FactoryException
     {
         updateDomainOfInterest(sourceCRS, targetCRS);
         final GeodeticDatum sourceDatum = sourceCRS.getDatum();
         final GeodeticDatum targetDatum = targetCRS.getDatum();
-        final CoordinateSystem sourceCS = sourceCRS.getCoordinateSystem();
-        final CoordinateSystem targetCS = targetCRS.getCoordinateSystem();
-        CoordinateSystem sourceNormalized = null;
-        CoordinateSystem targetNormalized = null;
-        Matrix           datumShift       = null;       // Those 3 variables 
will be null or non-null together.
+        Matrix datumShift = null;
         /*
-         * If both CRS use the same datum and the same prime meridian, then 
the coordinate operation is just
-         * an axis swapping, unit conversion or change between spherical and 
Cartesian coordinate system type.
+         * 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).
          * Otherwise (if the datum are not the same), we need to perform a 
scale, translation and rotation in
-         * Cartesian space using the Bursa-Wolf parameters.
+         * Cartesian space using the Bursa-Wolf parameters. If the user does 
not require the best accuracy,
+         * then the Molodensky approximation may be used for avoiding the 
conversion step to geocentric CRS.
          */
         Identifier identifier;
+        final DefaultMathTransformFactory mtFactory = 
factorySIS.getDefaultMathTransformFactory();
         if (equalsIgnoreMetadata(sourceDatum, targetDatum)) {
-            identifier = equalsIgnoreMetadata(sourceCS, targetCS) ? IDENTITY : 
AXIS_CHANGES;
+            identifier = AXIS_CHANGES;
         } else {
             identifier = ELLIPSOID_CHANGE;
             if (sourceDatum instanceof DefaultGeodeticDatum) {
                 datumShift = ((DefaultGeodeticDatum) 
sourceDatum).getPositionVectorTransformation(targetDatum, areaOfInterest);
                 if (datumShift != null) {
                     identifier = DATUM_SHIFT;
-                    sourceNormalized = 
CommonCRS.WGS84.geocentric().getCoordinateSystem();
-                    targetNormalized = sourceNormalized;
                 }
             }
+        }
+        /*
+         * If there is a change of prime meridian, concatenate that change 
before or after the datum shift.
+         * Actually we do not know if we should concatenate longitude rotation 
before or after datum shift.
+         * But this ambiguity does not apply to EPSG dataset 8.9 because 
source and target prime meridians
+         * are always Greenwich. For reducing ambiguity in other cases, the 
SIS DefaultGeodeticDatum class
+         * ensures that if the prime meridian are not the same, then the 
target meridian must be Greenwich.
+         */
+        final DefaultMathTransformFactory.Context context = 
ReferencingUtilities.createTransformContext(
+                sourceCRS, targetCRS, new MathTransformContext(sourceDatum, 
targetDatum));
+        /*
+         * Conceptually, all transformations below could done by first 
converting from the source coordinate
+         * system to geocentric Cartesian coordinates (X,Y,Z), apply an affine 
transform represented by the
+         * datum shift matrix, then convert from the (X′,Y′,Z′) coordinates to 
the target coordinate system.
+         * However there is two exceptions to this path:
+         *
+         *   1) In the particular where both the source and target CS are 
ellipsoidal, we may use the
+         *      Molodensky approximation as a shortcut (if the desired 
accuracy allows).
+         *   2) Even if we really go through the XYZ coordinates without 
Molodensky approximation, there is
+         *      at least 9 different ways to name this operation depending on 
whether the source and target
+         *      CRS are geocentric or geographic, 2- or 3-dimensional, whether 
there is a translation or not,
+         *      the rotation sign, etc. We try to use the most specific name 
if we can find one, and fallback
+         *      on an arbitrary name only in last resort.
+         */
+        final CoordinateSystem sourceCS = context.getSourceCS();
+        final CoordinateSystem targetCS = context.getTargetCS();
+        final Map<String,?> properties = properties(identifier);
+        MathTransform before = null, after = null;
+        ParameterValueGroup parameters;
+        if (datumShift != null) {
             /*
-             * If there is a change of prime meridian, concatenate that change 
before or after the datum shift.
-             * Actually we do not know if we should concatenate longitude 
rotation before or after datum shift.
-             * But this ambiguity does not apply to EPSG dataset 8.9 because 
source and target prime meridians
-             * are always Greenwich. For reducing ambiguity in other cases, 
the SIS DefaultGeodeticDatum class
-             * ensures that if the prime meridian are not the same, then the 
target meridian must be Greenwich.
+             * If the transform can be represented by a single coordinate 
operation, returns that operation.
+             * Possible operations are:
+             *
+             *    - Geocentric translation         (in geocentric, 
geographic-2D or geographic-3D domains)
+             *    - Position Vector transformation (in geocentric, 
geographic-2D or geographic-3D domains)
+             *
+             * Otherwise, maybe we failed to create the operation because the 
coordinate system type were not the same.
+             * Convert unconditionally to XYZ geocentric coordinates and apply 
the datum shift in that coordinate space.
              */
-            final double sourceMeridian = 
getGreenwichLongitude(sourceDatum.getPrimeMeridian(), NonSI.DEGREE_ANGLE);
-            final double targetMeridian = 
getGreenwichLongitude(targetDatum.getPrimeMeridian(), NonSI.DEGREE_ANGLE);
-            if (sourceMeridian != targetMeridian) {
-                if (sourceNormalized == null) {
-                    sourceNormalized = CoordinateSystems.replaceAxes(sourceCS, 
AxesConvention.NORMALIZED);
-                    targetNormalized = CoordinateSystems.replaceAxes(targetCS, 
AxesConvention.NORMALIZED);
-                }
-                final boolean isTargetCartesian = (targetNormalized instanceof 
CartesianCS);
-                if (!(isTargetCartesian || targetNormalized instanceof 
SphericalCS)) {
-                    throw new 
FactoryException(Errors.format(Errors.Keys.IllegalCoordinateSystem_1, 
targetCS.getClass()));
-                }
-                final Matrix4 rot = new Matrix4();
-                boolean isSource = true;
-                do {                                // Executed exactly twice: 
once for source, then once for target.
-                    double θ = isSource ? sourceMeridian : -targetMeridian;
-                    if (θ != 0) {
-                        if (isTargetCartesian) {
-                            θ = Math.toRadians(θ);
-                            rot.m00 =   rot.m11 = Math.cos(θ);
-                            rot.m01 = -(rot.m10 = Math.sin(θ));
-                        } else {
-                            rot.m02 = θ;
-                        }
-                        if (datumShift == null) {
-                            datumShift = rot;
-                        } else if (isSource) {
-                            datumShift = Matrices.multiply(datumShift, rot);   
 // Apply rotation before datum shift.
-                        } else {
-                            datumShift = Matrices.multiply(rot, datumShift);   
 // Apply rotation after datum shift.
-                        }
-                    }
-                } while ((isSource = !isSource) == false);
+            parameters = GeocentricAffine.createParameters(sourceCS, targetCS, 
datumShift, desiredAccuracy >= MOLODENSKY_ACCURACY);
+            if (parameters == null) {
+                parameters = 
TensorParameters.WKT1.createValueGroup(properties, datumShift);
+                final CoordinateSystem normalized = 
CommonCRS.WGS84.geocentric().getCoordinateSystem();
+                before = mtFactory.createCoordinateSystemChange(sourceCS, 
normalized);
+                after  = mtFactory.createCoordinateSystemChange(normalized, 
targetCS);
+                context.setSource(normalized);
+                context.setTarget(normalized);
             }
+        } else {
+            parameters = TensorParameters.WKT1.createValueGroup(properties);   
             // Initialized to identity.
+            
parameters.parameter(Constants.NUM_COL).setValue(sourceCS.getDimension() + 1);
+            
parameters.parameter(Constants.NUM_ROW).setValue(targetCS.getDimension() + 1);
+            before = mtFactory.createCoordinateSystemChange(sourceCS, 
targetCS);
+            context.setSource(targetCS);
         }
         /*
          * Transform between differents datums using Bursa Wolf parameters. 
The Bursa Wolf parameters are used
          * with "standard" geocentric CS, i.e. with X axis towards the prime 
meridian, Y axis towards East and
-         * Z axis toward North. The following steps are applied:
+         * Z axis toward North, unless the Molodensky approximation is used. 
The following steps are applied:
          *
          *     source CRS                        →
          *     normalized CRS with source datum  →
          *     normalized CRS with target datum  →
          *     target CRS
+         *
+         * Those steps may be either explicit with the 'before' and 'after' 
transform, or implicit with the
+         * Context parameter.
          */
-        MathTransform tr;
-        final DefaultMathTransformFactory mtFactory = 
factorySIS.getDefaultMathTransformFactory();
-        if (datumShift != null) {
-            final MathTransform normalize   = 
mtFactory.createCoordinateSystemChange(sourceCS, sourceNormalized);
-            final MathTransform denormalize = 
mtFactory.createCoordinateSystemChange(targetNormalized, targetCS);
-            tr = mtFactory.createAffineTransform(datumShift);
-            tr = mtFactory.createConcatenatedTransform(normalize,
-                 mtFactory.createConcatenatedTransform(tr, denormalize));
-        } else {
-            tr = mtFactory.createCoordinateSystemChange(sourceCS, targetCS);
+        MathTransform transform = 
mtFactory.createParameterizedTransform(parameters, context);
+        final OperationMethod method = mtFactory.getLastMethodUsed();
+        if (before != null) {
+            transform = mtFactory.createConcatenatedTransform(before, 
transform);
+            if (after != null) {
+                transform = mtFactory.createConcatenatedTransform(transform, 
after);
+            }
         }
-        return createFromMathTransform(properties(identifier), sourceCRS, 
targetCRS, tr, null, null);
+        return createFromMathTransform(properties, sourceCRS, targetCRS, 
transform, method, null);
     }
 
     /**

Added: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java?rev=1736638&view=auto
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
 (added)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
 [UTF-8] Fri Mar 25 18:23:14 2016
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation;
+
+import javax.measure.unit.NonSI;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.cs.CartesianCS;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.datum.GeodeticDatum;
+import org.opengis.referencing.operation.Matrix;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.matrix.Matrix4;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import 
org.apache.sis.referencing.operation.transform.ContextualParameters.MatrixRole;
+import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory.Context;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * Information about the context in which a {@code MathTransform} is created.
+ * This class performs the same normalization than the super-class (namely 
axis swapping and unit conversions),
+ * with the addition of longitude rotation for supporting change of prime 
meridian.  This later change is not
+ * applied by the super-class because prime meridian is part of geodetic 
datum, and the public math transform
+ * factory know nothing about datum (on design, for separation of concerns).
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+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.
+     */
+    private double sourceMeridian, targetMeridian;
+
+    /**
+     * Creates a new context which add some datum-related information in 
addition
+     * to the information provided by the super-class.
+     */
+    MathTransformContext(final GeodeticDatum source, final GeodeticDatum 
target) {
+        final double rs = 
ReferencingUtilities.getGreenwichLongitude(source.getPrimeMeridian(), 
NonSI.DEGREE_ANGLE);
+        final double rt = 
ReferencingUtilities.getGreenwichLongitude(target.getPrimeMeridian(), 
NonSI.DEGREE_ANGLE);
+        if (rs != rt) {
+            sourceMeridian = rs;
+            targetMeridian = rt;
+        }
+    }
+
+    /**
+     * Returns the normalization or denormalization matrix.
+     */
+    @Override
+    @SuppressWarnings("fallthrough")
+    public Matrix getMatrix(final MatrixRole role) throws FactoryException {
+        final CoordinateSystem cs;
+        boolean inverse = false;
+        double rotation;
+        switch (role) {
+            default: throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, 
"role", role));
+            case INVERSE_NORMALIZATION:   inverse  = true;              // 
Fall through
+            case NORMALIZATION:           rotation = sourceMeridian;
+                                          cs       = getSourceCS();
+                                          break;
+            case INVERSE_DENORMALIZATION: inverse  = true;              // 
Fall through
+            case DENORMALIZATION:         inverse  = !inverse;
+                                          rotation = targetMeridian;
+                                          cs       = getTargetCS();
+                                          break;
+        }
+        Matrix matrix = super.getMatrix(role);
+        if (rotation != 0) {
+            if (inverse) rotation = -rotation;
+            MatrixSIS cm = MatrixSIS.castOrCopy(matrix);
+            if (cs instanceof CartesianCS) {
+                rotation = Math.toRadians(rotation);
+                final Matrix4 rot = new Matrix4();
+                rot.m00 =   rot.m11 = Math.cos(rotation);
+                rot.m01 = -(rot.m10 = Math.sin(rotation));
+                if (inverse) {
+                    matrix = Matrices.multiply(rot, cm);        // Apply the 
rotation after denormalization.
+                } else {
+                    matrix = cm.multiply(rot);                  // Apply the 
rotation before normalization.
+                }
+            } else {
+                final Double value = rotation;
+                if (inverse) {
+                    cm.convertAfter(0, null, value);            // Longitude 
is the first axis in normalized CS.
+                } else {
+                    cm.convertBefore(0, null, value);
+                }
+                matrix = cm;
+            }
+        }
+        return matrix;
+    }
+}

Propchange: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
 [UTF-8] Fri Mar 25 18:23:14 2016
@@ -71,7 +71,6 @@ import org.apache.sis.referencing.cs.Coo
 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.referencing.operation.matrix.NoninvertibleMatrixException;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Classes;
@@ -636,33 +635,29 @@ public class DefaultMathTransformFactory
          */
         @SuppressWarnings("fallthrough")
         public Matrix getMatrix(final ContextualParameters.MatrixRole role) 
throws FactoryException {
-            final CoordinateSystem source, target;
+            final CoordinateSystem specified;
             boolean inverse = false;
             switch (role) {
-                case INVERSE_NORMALIZATION: inverse = true;         // Fall 
through
-                case NORMALIZATION: {
-                    source = getSourceCS(); if (source == null) return null;
-                    target = CoordinateSystems.replaceAxes(source, 
AxesConvention.NORMALIZED);
-                    break;
-                }
-                case INVERSE_DENORMALIZATION: inverse = true;       // Fall 
through
-                case DENORMALIZATION: {
-                    target = getTargetCS(); if (target == null) return null;
-                    source = CoordinateSystems.replaceAxes(target, 
AxesConvention.NORMALIZED);
-                    break;
-                }
                 default: throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, 
"role", role));
+                case INVERSE_NORMALIZATION:   inverse   = true;          // 
Fall through
+                case NORMALIZATION:           specified = getSourceCS(); break;
+                case INVERSE_DENORMALIZATION: inverse   = true;          // 
Fall through
+                case DENORMALIZATION:         inverse   = !inverse;
+                                              specified = getTargetCS(); break;
+            }
+            if (specified == null) {
+                return null;
             }
-            Matrix matrix;
+            final CoordinateSystem normalized = 
CoordinateSystems.replaceAxes(specified, AxesConvention.NORMALIZED);
             try {
-                matrix = CoordinateSystems.swapAndScaleAxes(source, target);
                 if (inverse) {
-                    matrix = Matrices.inverse(matrix);
+                    return CoordinateSystems.swapAndScaleAxes(normalized, 
specified);
+                } else {
+                    return CoordinateSystems.swapAndScaleAxes(specified, 
normalized);
                 }
-            } catch (IllegalArgumentException | ConversionException | 
NoninvertibleMatrixException cause) {
+            } catch (IllegalArgumentException | ConversionException cause) {
                 throw new 
InvalidGeodeticParameterException(cause.getLocalizedMessage(), cause);
             }
-            return matrix;
         }
 
         /**
@@ -1212,7 +1207,11 @@ public class DefaultMathTransformFactory
      */
     @Override
     public MathTransform createAffineTransform(final Matrix matrix) throws 
FactoryException {
-        lastMethod.remove(); // To be strict, we should set the 
ProjectiveTransform provider.
+        /*
+         * Performance note: we could set lastMethod to the "Affine" operation 
method provider, but we do not
+         * because setting this value is not free (e.g. it depends on matrix 
size) and it is rarely needed.
+         */
+        lastMethod.remove();
         return unique(MathTransforms.linear(matrix));
     }
 


Reply via email to