sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1736638 - in /sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis: internal/referencing/provider/ parameter/ referencing/datum/ referencing/operation/ referencing/operation/transform/
Date Fri, 25 Mar 2016 18:23:14 GMT
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));
     }
 



Mime
View raw message