You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2016/03/30 14:35:11 UTC

svn commit: r1737107 [2/6] - in /sis/trunk: ./ application/sis-console/src/main/artifact/conf/ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/ core/sis-metadata/src...

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java [UTF-8] Wed Mar 30 12:35:10 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}.
@@ -165,6 +177,16 @@ public abstract class GeocentricAffine e
     abstract int getType();
 
     /**
+     * The inverse of this operation is the same operation with parameter signs inverted.
+     *
+     * @return {@code true} for all {@code GeocentricAffine}.
+     */
+    @Override
+    public final boolean isInvertible() {
+        return true;
+    }
+
+    /**
      * Creates a math transform from the specified group of parameter values.
      * The default implementation creates an affine transform, but some subclasses
      * will wrap that affine operation into Geographic/Geocentric conversions.
@@ -200,6 +222,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 +330,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 +339,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 +369,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/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java [UTF-8] Wed Mar 30 12:35:10 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/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeographicOffsets.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeographicOffsets.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeographicOffsets.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeographicOffsets.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -101,6 +101,16 @@ public class GeographicOffsets extends A
     }
 
     /**
+     * The inverse of this operation is the same operation with parameter signs inverted.
+     *
+     * @return {@code true} for all {@code GeocentricAffine}.
+     */
+    @Override
+    public final boolean isInvertible() {
+        return true;
+    }
+
+    /**
      * Creates a transform from the specified group of parameter values.
      * The parameter values are unconditionally converted to degrees and metres.
      *

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -678,7 +678,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;
@@ -736,6 +736,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) {
@@ -754,6 +756,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) {
@@ -761,7 +765,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/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AuthorityFactories.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AuthorityFactories.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AuthorityFactories.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AuthorityFactories.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -110,7 +110,7 @@ final class AuthorityFactories<T extends
             if (factory == null) try {
                 factory = new EPSGFactory(null);
             } catch (FactoryException e) {
-                log(Level.CONFIG, e);
+                log(e, false);
                 factory = EPSGFactoryFallback.INSTANCE;
             }
             EPSG[0] = factory;
@@ -137,37 +137,42 @@ final class AuthorityFactories<T extends
                 EPSG[0] = factory;
             }
         }
-        log(Level.WARNING, e);
+        log(e, true);
         return factory;
     }
 
     /**
      * Notifies that a factory is unavailable, but without giving a fallback and without logging.
      * The caller is responsible for logging a warning and to provide its own fallback.
+     *
+     * @return {@code true} on success, or {@code false} if this method did nothing.
      */
-    static void failure(final UnavailableFactoryException e) {
+    static boolean failure(final UnavailableFactoryException e) {
         if (!(e.getCause() instanceof SQLTransientException)) {
             final AuthorityFactory unavailable = e.getUnavailableFactory();
             synchronized (EPSG) {
                 if (unavailable == EPSG[0]) {
                     ALL.reload();
                     EPSG[0] = EPSGFactoryFallback.INSTANCE;
+                    return true;
                 }
             }
         }
+        return false;
     }
 
     /**
      * Logs the given exception at the given level. This method pretends that the logging come from
      * {@link CRS#getAuthorityFactory(String)}, which is the public facade for {@link #EPSG()}.
      */
-    private static void log(final Level level, final Exception e) {
+    private static void log(final Exception e, final boolean isWarning) {
         String message = e.getLocalizedMessage();
         if (message == null) {
             message = e.toString();
         }
-        final LogRecord record = new LogRecord(level, message);
+        final LogRecord record = new LogRecord(isWarning ? Level.WARNING : Level.CONFIG, message);
         record.setLoggerName(Loggers.CRS_FACTORY);
+        if (isWarning) record.setThrown(e);
         Logging.log(CRS.class, "getAuthorityFactory", record);
     }
 

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -42,6 +42,7 @@ import org.opengis.referencing.crs.Engin
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.opengis.referencing.operation.CoordinateOperation;
 import org.apache.sis.internal.metadata.AxisDirections;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.system.DefaultFactories;
@@ -50,8 +51,12 @@ import org.apache.sis.referencing.cs.Def
 import org.apache.sis.referencing.crs.DefaultGeographicCRS;
 import org.apache.sis.referencing.crs.DefaultVerticalCRS;
 import org.apache.sis.referencing.crs.DefaultCompoundCRS;
-import org.apache.sis.metadata.iso.extent.Extents;
+import org.apache.sis.referencing.operation.CoordinateOperationContext;
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
 import org.apache.sis.referencing.factory.UnavailableFactoryException;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.metadata.iso.extent.Extents;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Static;
@@ -591,4 +596,43 @@ check:  while (lower != 0 || upper != di
         }
         return AuthorityFactories.ALL.getAuthorityFactory(CRSAuthorityFactory.class, authority, null);
     }
+
+    /**
+     * Finds a mathematical operation that transforms or converts coordinates from the given source to the
+     * given target coordinate reference system. If an estimation of the geographic area containing the points
+     * to transform is known, it can be specified for helping this method to find a better suited operation.
+     *
+     * <p>Note that the area of interest is just one aspect that may affect the coordinate operation.
+     * Other aspects are the time of interest (because some coordinate operations take in account the
+     * plate tectonics movement) or the desired accuracy. For more control on the coordinate operation
+     * to create, see {@link CoordinateOperationContext}.</p>
+     *
+     * @param  sourceCRS      the CRS of source coordinates.
+     * @param  targetCRS      the CRS of target coordinates.
+     * @param  areaOfInterest the area of interest, or {@code null} if none.
+     * @return the mathematical operation from {@code sourceCRS} to {@code targetCRS}.
+     * @throws FactoryException if the operation can not be created.
+     *
+     * @see DefaultCoordinateOperationFactory#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem, CoordinateOperationContext)
+     *
+     * @since 0.7
+     */
+    public static CoordinateOperation findOperation(final CoordinateReferenceSystem sourceCRS,
+                                                    final CoordinateReferenceSystem targetCRS,
+                                                    final GeographicBoundingBox areaOfInterest)
+            throws FactoryException
+    {
+        ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS);
+        ArgumentChecks.ensureNonNull("targetCRS", targetCRS);
+        CoordinateOperationContext context = null;
+        if (areaOfInterest != null) {
+            final DefaultGeographicBoundingBox bbox = DefaultGeographicBoundingBox.castOrCopy(areaOfInterest);
+            if (bbox.isEmpty()) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "areaOfInterest"));
+            }
+            context = new CoordinateOperationContext();
+            context.setGeographicBoundingBox(bbox);
+        }
+        return CoordinateOperations.factory.createOperation(sourceCRS, targetCRS, context);
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -20,6 +20,8 @@ import java.util.Map;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
 import javax.measure.unit.SI;
 import javax.measure.unit.NonSI;
 import javax.measure.unit.Unit;
@@ -38,8 +40,10 @@ import org.opengis.referencing.crs.CRSAu
 import org.opengis.referencing.cs.TimeCS;
 import org.opengis.referencing.cs.VerticalCS;
 import org.opengis.referencing.cs.CartesianCS;
+import org.opengis.referencing.cs.SphericalCS;
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CSAuthorityFactory;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.datum.PrimeMeridian;
@@ -65,8 +69,10 @@ import org.apache.sis.internal.system.Sy
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.Exceptions;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Units;
@@ -279,6 +285,11 @@ public enum CommonCRS {
     static final CommonCRS DEFAULT = WGS84;
 
     /**
+     * Properties to exclude when using an other object as a template.
+     */
+    private static final String[] EXCLUDE = new String[] {IdentifiedObject.IDENTIFIERS_KEY};
+
+    /**
      * The EPSG code of the two-dimensional geographic CRS.
      */
     final short geographic;
@@ -337,13 +348,20 @@ public enum CommonCRS {
     private transient volatile GeographicCRS cachedGeo3D;
 
     /**
-     * The geocentric CRS, created when first needed.
+     * The geocentric CRS using Cartesian coordinate system, created when first needed.
      *
      * @see #geocentric()
      */
     private transient volatile GeocentricCRS cachedGeocentric;
 
     /**
+     * The geocentric CRS using spherical coordinate system, created when first needed.
+     *
+     * @see #spherical()
+     */
+    private transient volatile GeocentricCRS cachedSpherical;
+
+    /**
      * The Universal Transverse Mercator projections, created when first needed.
      * All accesses to this map shall be synchronized on {@code cachedUTM}.
      *
@@ -505,7 +523,7 @@ public enum CommonCRS {
                         cached = object = factory.createGeographicCRS(String.valueOf(geographic));
                         return object;
                     } catch (FactoryException e) {
-                        failure(this, "geographic", e);
+                        failure(this, "geographic", e, geographic);
                     }
                     /*
                      * All constants defined in this enumeration use the same coordinate system, EPSG:6422.
@@ -561,7 +579,7 @@ public enum CommonCRS {
                             cachedGeo3D = object = factory.createGeographicCRS(String.valueOf(geo3D));
                             return object;
                         } catch (FactoryException e) {
-                            failure(this, "geographic3D", e);
+                            failure(this, "geographic3D", e, geo3D);
                         }
                     }
                     /*
@@ -619,7 +637,7 @@ public enum CommonCRS {
                             cachedGeocentric = object = factory.createGeocentricCRS(String.valueOf(geocentric));
                             return object;
                         } catch (FactoryException e) {
-                            failure(this, "geocentric", e);
+                            failure(this, "geocentric", e, geocentric);
                         }
                     }
                     /*
@@ -644,6 +662,56 @@ public enum CommonCRS {
     }
 
     /**
+     * Returns the geocentric CRS using a spherical coordinate system. Axes are:
+     *
+     * <ol>
+     *   <li>Spherical latitude in degrees oriented toward {@linkplain AxisDirection#NORTH north}.</li>
+     *   <li>Spherical longitude in degrees oriented toward {@linkplain AxisDirection#EAST east}.</li>
+     *   <li>Geocentric radius in metres oriented toward {@linkplain AxisDirection#UP up}.</li>
+     * </ol>
+     *
+     * @return The geocentric CRS associated to this enum.
+     *
+     * @see DefaultGeocentricCRS
+     *
+     * @since 0.7
+     */
+    public GeocentricCRS spherical() {
+        GeocentricCRS object = cachedSpherical;
+        if (object == null) {
+            synchronized (this) {
+                object = cachedSpherical;
+                if (object == null) {
+                    /*
+                     * All constants defined in this enumeration use the same coordinate system, EPSG:6404.
+                     * We will arbitrarily create this CS only for the most frequently created CRS,
+                     * and share that CS instance for all other constants.
+                     */
+                    SphericalCS cs = null;
+                    if (this == DEFAULT) {
+                        final CSAuthorityFactory factory = csFactory();
+                        if (factory != null) try {
+                            cs = factory.createSphericalCS("6404");
+                        } catch (FactoryException e) {
+                            failure(this, "spherical", e, (short) 6404);
+                        }
+                        if (cs == null) {
+                            cs = (SphericalCS) StandardDefinitions.createCoordinateSystem((short) 6404);
+                        }
+                    } else {
+                        cs = (SphericalCS) DEFAULT.spherical().getCoordinateSystem();
+                    }
+                    // Use same name and datum than the geographic CRS.
+                    final GeographicCRS base = geographic();
+                    object = new DefaultGeocentricCRS(IdentifiedObjects.getProperties(base, EXCLUDE), base.getDatum(), cs);
+                    cachedSpherical = object;
+                }
+            }
+        }
+        return object;
+    }
+
+    /**
      * Returns the geodetic datum associated to this geodetic object.
      * The following table summarizes the datums known to this class,
      * together with an enumeration value that can be used for fetching that datum:
@@ -675,7 +743,7 @@ public enum CommonCRS {
                         cached = object = factory.createGeodeticDatum(String.valueOf(datum));
                         return object;
                     } catch (FactoryException e) {
-                        failure(this, "datum", e);
+                        failure(this, "datum", e, datum);
                     }
                     object = StandardDefinitions.createGeodeticDatum(datum, ellipsoid(), primeMeridian());
                     cached = object;
@@ -719,7 +787,7 @@ public enum CommonCRS {
                             cached = object = factory.createEllipsoid(String.valueOf(ellipsoid));
                             return object;
                         } catch (FactoryException e) {
-                            failure(this, "ellipsoid", e);
+                            failure(this, "ellipsoid", e, ellipsoid);
                         }
                         object = StandardDefinitions.createEllipsoid(ellipsoid);
                     }
@@ -759,7 +827,7 @@ public enum CommonCRS {
                             cached = object = factory.createPrimeMeridian(StandardDefinitions.GREENWICH);
                             return object;
                         } catch (FactoryException e) {
-                            failure(this, "primeMeridian", e);
+                            failure(this, "primeMeridian", e, (short) 8901);
                         }
                         object = StandardDefinitions.primeMeridian();
                     }
@@ -881,7 +949,7 @@ public enum CommonCRS {
                     if (factory != null) try {
                         return factory.createProjectedCRS(String.valueOf(code));
                     } catch (FactoryException e) {
-                        failure(this, "UTM", e);
+                        failure(this, "UTM", e, code);
                     }
                 }
             }
@@ -1131,12 +1199,12 @@ public enum CommonCRS {
                                 cached = object = factory.createVerticalCRS(String.valueOf(crs));
                                 return object;
                             } catch (FactoryException e) {
-                                failure(this, "crs", e);
+                                failure(this, "crs", e, crs);
                             }
                             object = StandardDefinitions.createVerticalCRS(crs, datum());
                         } else {
                             final VerticalCS cs = cs();
-                            object = new DefaultVerticalCRS(IdentifiedObjects.getProperties(cs), datum(), cs);
+                            object = new DefaultVerticalCRS(IdentifiedObjects.getProperties(cs, EXCLUDE), datum(), cs);
                         }
                         cached = object;
                     }
@@ -1196,7 +1264,7 @@ public enum CommonCRS {
                                 cached = object = factory.createVerticalDatum(String.valueOf(datum));
                                 return object;
                             } catch (FactoryException e) {
-                                failure(this, "datum", e);
+                                failure(this, "datum", e, datum);
                             }
                             object = StandardDefinitions.createVerticalDatum(datum);
                         } else {
@@ -1374,7 +1442,7 @@ public enum CommonCRS {
                     object = crs(cached);
                     if (object == null) {
                         final TemporalDatum datum = datum();
-                        object = new DefaultTemporalCRS(IdentifiedObjects.getProperties(datum), datum, cs());
+                        object = new DefaultTemporalCRS(IdentifiedObjects.getProperties(datum, EXCLUDE), datum, cs());
                         cached = object;
                     }
                 }
@@ -1402,8 +1470,8 @@ public enum CommonCRS {
                 case UNIX: {
                     // Share the NamedIdentifier created for Java time.
                     final TimeCS share = JAVA.crs().getCoordinateSystem();
-                    cs   = IdentifiedObjects.getProperties(share);
-                    axis = IdentifiedObjects.getProperties(share.getAxis(0));
+                    cs   = IdentifiedObjects.getProperties(share, EXCLUDE);
+                    axis = IdentifiedObjects.getProperties(share.getAxis(0), EXCLUDE);
                     break;
                 }
                 case JAVA: {
@@ -1503,7 +1571,7 @@ public enum CommonCRS {
      * Returns the same properties than the given object, except for the identifier which is set to the given code.
      */
     private static Map<String,?> properties(final IdentifiedObject template, final short code) {
-        final Map<String,Object> properties = new HashMap<String,Object>(IdentifiedObjects.getProperties(template));
+        final Map<String,Object> properties = new HashMap<String,Object>(IdentifiedObjects.getProperties(template, EXCLUDE));
         properties.put(GeographicCRS.IDENTIFIERS_KEY, new NamedIdentifier(Citations.EPSG, String.valueOf(code)));
         return properties;
     }
@@ -1523,6 +1591,20 @@ public enum CommonCRS {
     }
 
     /**
+     * Returns the EPSG factory to use for creating coordinate systems, or {@code null} if none.
+     * If this method returns {@code null}, then the caller will silently fallback on hard-coded values.
+     */
+    static CSAuthorityFactory csFactory() {
+        if (!EPSGFactoryFallback.FORCE_HARDCODED) {
+            final AuthorityFactory factory = AuthorityFactories.EPSG();
+            if (!(factory instanceof EPSGFactoryFallback)) {
+                return (CSAuthorityFactory) factory;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns the EPSG factory to use for creating datum, ellipsoids and prime meridians, or {@code null} if none.
      * If this method returns {@code null}, then the caller will silently fallback on hard-coded values.
      */
@@ -1540,10 +1622,15 @@ public enum CommonCRS {
      * Invoked when a factory failed to create an object.
      * After invoking this method, the caller will fallback on hard-coded values.
      */
-    static void failure(final Object caller, final String method, final FactoryException e) {
-        if (e instanceof UnavailableFactoryException) {
-            AuthorityFactories.failure((UnavailableFactoryException) e);
+    static void failure(final Object caller, final String method, final FactoryException e, final int code) {
+        String message = Errors.format(Errors.Keys.CanNotInstantiate_1, "EPSG:" + code);
+        message = Exceptions.formatChainedMessages(null, message, e);
+        final LogRecord record = new LogRecord(Level.WARNING, message);
+        if (!(e instanceof UnavailableFactoryException) || !AuthorityFactories.failure((UnavailableFactoryException) e)) {
+            // Append the stack trace only if the exception is the the one we expect when the factory is not available.
+            record.setThrown(e);
         }
-        Logging.unexpectedException(Logging.getLogger(Loggers.CRS_FACTORY), caller.getClass(), method, e);
+        record.setLoggerName(Loggers.CRS_FACTORY);
+        Logging.log(caller.getClass(), method, record);
     }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -91,6 +91,10 @@ public final class IdentifiedObjects ext
      *       <td>{@link CoordinateOperation#getOperationVersion()}</td></tr>
      *   <tr><td>{@value org.opengis.referencing.operation.CoordinateOperation#COORDINATE_OPERATION_ACCURACY_KEY}</td>
      *       <td>{@link CoordinateOperation#getCoordinateOperationAccuracy()}</td></tr>
+     *   <tr><td>{@value org.opengis.referencing.operation.OperationMethod#FORMULA_KEY}</td>
+     *       <td>{@link org.opengis.referencing.operation.OperationMethod#getFormula()}</td></tr>
+     *   <tr><td>{@value org.apache.sis.referencing.AbstractIdentifiedObject#DEPRECATED_KEY}</td>
+     *       <td>{@link AbstractIdentifiedObject#isDeprecated()}</td></tr>
      * </table>
      *
      * <div class="note"><b>Note:</b>

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/Properties.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/Properties.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/Properties.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/Properties.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -26,8 +26,10 @@ import org.opengis.referencing.Reference
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.metadata.quality.PositionalAccuracy;
 import org.apache.sis.internal.util.AbstractMap;
+import org.apache.sis.util.Deprecable;
 
 
 /**
@@ -40,7 +42,7 @@ import org.apache.sis.internal.util.Abst
  *
  * @author  Martin Desruisseaux (IRD)
  * @since   0.4
- * @version 0.4
+ * @version 0.7
  * @module
  */
 final class Properties extends AbstractMap<String,Object> implements Serializable {
@@ -55,14 +57,16 @@ final class Properties extends AbstractM
      * of {@link #INDICES}.
      */
     private static final String[] KEYS = {
-        /*[0]*/ IdentifiedObject    .NAME_KEY,
-        /*[1]*/ IdentifiedObject    .IDENTIFIERS_KEY,
-        /*[2]*/ IdentifiedObject    .ALIAS_KEY,
-        /*[3]*/ IdentifiedObject    .REMARKS_KEY,
-        /*[4]*/ CoordinateOperation .SCOPE_KEY,              // same in Datum and ReferenceSystem
-        /*[5]*/ CoordinateOperation .DOMAIN_OF_VALIDITY_KEY, // same in Datum and ReferenceSystem
-        /*[6]*/ CoordinateOperation .OPERATION_VERSION_KEY,
-        /*[7]*/ CoordinateOperation .COORDINATE_OPERATION_ACCURACY_KEY
+        /*[0]*/ IdentifiedObject        .NAME_KEY,
+        /*[1]*/ IdentifiedObject        .IDENTIFIERS_KEY,
+        /*[2]*/ IdentifiedObject        .ALIAS_KEY,
+        /*[3]*/ IdentifiedObject        .REMARKS_KEY,
+        /*[4]*/ CoordinateOperation     .SCOPE_KEY,                     // same in Datum and ReferenceSystem
+        /*[5]*/ CoordinateOperation     .DOMAIN_OF_VALIDITY_KEY,        // same in Datum and ReferenceSystem
+        /*[6]*/ CoordinateOperation     .OPERATION_VERSION_KEY,
+        /*[7]*/ CoordinateOperation     .COORDINATE_OPERATION_ACCURACY_KEY,
+        /*[8]*/ OperationMethod         .FORMULA_KEY,
+        /*[9]*/ AbstractIdentifiedObject.DEPRECATED_KEY
 
         /*
          * The current implementation does not look for minimum and maximum values in ParameterDescriptor
@@ -182,6 +186,18 @@ final class Properties extends AbstractM
                     }
                     break;
                 }
+                case 8: {   // FORMULA_KEY
+                    if (object instanceof OperationMethod) {
+                        return ((OperationMethod) object).getFormula();
+                    }
+                    break;
+                }
+                case 9: {   // DEPRECATED_KEY
+                    if (object instanceof Deprecable) {
+                        return ((Deprecable) object).isDeprecated();
+                    }
+                    break;
+                }
                 default: throw new AssertionError(key);
             }
         }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/StandardDefinitions.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/StandardDefinitions.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/StandardDefinitions.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/StandardDefinitions.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -51,6 +51,7 @@ import org.apache.sis.referencing.datum.
 import org.apache.sis.referencing.datum.DefaultVerticalDatum;
 import org.apache.sis.referencing.cs.DefaultVerticalCS;
 import org.apache.sis.referencing.cs.DefaultCartesianCS;
+import org.apache.sis.referencing.cs.DefaultSphericalCS;
 import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
 import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
 import org.apache.sis.referencing.crs.DefaultGeographicCRS;
@@ -311,12 +312,14 @@ final class StandardDefinitions {
     @SuppressWarnings("fallthrough")
     static CoordinateSystem createCoordinateSystem(final short code) {
         final String name;
-        final int dim;  // Number of dimension.
-        short axisCode; // Code of first axis + dim (or code after the last axis).
+        final int dim;                  // Number of dimension.
+        short axisCode;                 // Code of first axis + dim (or code after the last axis).
         boolean isCartesian = false;
+        boolean isSpherical = false;
         switch (code) {
             case 6422: name = "Ellipsoidal 2D"; dim = 2; axisCode = 108; break;
             case 6423: name = "Ellipsoidal 3D"; dim = 3; axisCode = 111; break;
+            case 6404: name = "Spherical";      dim = 3; axisCode =  63; isSpherical = true; break;
             case 6500: name = "Earth centred";  dim = 3; axisCode = 118; isCartesian = true; break;
             case 4400: name = "Cartesian 2D";   dim = 2; axisCode =   3; isCartesian = true; break;
             default:   throw new AssertionError(code);
@@ -336,6 +339,8 @@ final class StandardDefinitions {
             } else {
                 return new DefaultCartesianCS(properties, xAxis, yAxis);
             }
+        } else if (isSpherical) {
+            return new DefaultSphericalCS(properties, xAxis, yAxis, zAxis);
         } else {
             if (zAxis != null) {
                 return new DefaultEllipsoidalCS(properties, xAxis, yAxis, zAxis);
@@ -369,6 +374,29 @@ final class StandardDefinitions {
                        unit = SI.METRE;
                        dir  = AxisDirection.NORTH;
                        break;
+            case 60:   name = "Spherical latitude";
+                       abrv = "φ′";                         // See HardCodedAxes.SPHERICAL_LATITUDE in tests.
+                       unit = NonSI.DEGREE_ANGLE;
+                       dir  = AxisDirection.NORTH;
+                       min  = Latitude.MIN_VALUE;
+                       max  = Latitude.MAX_VALUE;
+                       rm   = RangeMeaning.EXACT;
+                       break;
+            case 61:   name = "Spherical longitude";
+                       abrv = "θ";                          // See HardCodedAxes.SPHERICAL_LONGITUDE in tests.
+                       unit = NonSI.DEGREE_ANGLE;
+                       dir  = AxisDirection.EAST;
+                       min  = Longitude.MIN_VALUE;
+                       max  = Longitude.MAX_VALUE;
+                       rm   = RangeMeaning.WRAPAROUND;
+                       break;
+            case 62:   name = "Geocentric radius";
+                       abrv = "R";                          // See HardCodedAxes.GEOCENTRIC_RADIUS in tests.
+                       unit = SI.METRE;
+                       dir  = AxisDirection.UP;
+                       rm   = RangeMeaning.EXACT;
+                       min  = 0;
+                       break;
             case 108:  // Used in Ellipsoidal 3D.
             case 106:  name = AxisNames.GEODETIC_LATITUDE;
                        abrv = "φ";

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -108,7 +108,7 @@ import org.apache.sis.measure.Units;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4
- * @version 0.6
+ * @version 0.7
  * @module
  *
  * @see AbstractCS#forConvention(AxesConvention)
@@ -153,7 +153,13 @@ public enum AxesConvention implements Ax
         }
 
         @Override
+        @Deprecated
         public Unit<?> getUnitReplacement(Unit<?> unit) {
+            return getUnitReplacement(null, unit);
+        }
+
+        @Override
+        public Unit<?> getUnitReplacement(final CoordinateSystemAxis axis, Unit<?> unit) {
             if (Units.isLinear(unit)) {
                 unit = SI.METRE;
             } else if (Units.isAngular(unit)) {
@@ -164,18 +170,35 @@ public enum AxesConvention implements Ax
             return unit;
         }
 
-        /*
-         * Same policy than AxesConvention.CONVENTIONALLY_ORIENTATED.
-         */
         @Override
+        @Deprecated
         public AxisDirection getDirectionReplacement(final AxisDirection direction) {
-            return AxisDirections.isIntercardinal(direction) ? direction : AxisDirections.absolute(direction);
+            return getDirectionReplacement(null, direction);
+        }
+
+        @Override
+        public AxisDirection getDirectionReplacement(final CoordinateSystemAxis axis, final AxisDirection direction) {
+            /*
+             * For now we do not touch to inter-cardinal directions (e.g. "North-East")
+             * because it is not clear which normalization policy would match common usage.
+             */
+            if (!AxisDirections.isIntercardinal(direction)) {
+                /*
+                 * Change the direction only if the axis allows negative values.
+                 * If the axis accepts only positive values, then the direction
+                 * is considered non-invertible.
+                 */
+                if (axis == null || axis.getMinimumValue() < 0) {
+                    return AxisDirections.absolute(direction);
+                }
+            }
+            return direction;
         }
     },
 
     /**
      * Axes are oriented toward conventional directions and ordered for a {@linkplain #RIGHT_HANDED right-handed}
-     * coordinate system. Ranges of ordinate values and units of measurement are unchanged.
+     * coordinate system. Units of measurement are unchanged.
      *
      * <p>More specifically, directions opposites to the following ones are replaced by their "forward" counterpart
      * (e.g. {@code SOUTH} → {@code NORTH}):</p>
@@ -227,17 +250,25 @@ public enum AxesConvention implements Ax
         }
 
         @Override
+        @Deprecated
         public Unit<?> getUnitReplacement(final Unit<?> unit) {
             return unit;
         }
 
-        /*
-         * For now we do not touch to inter-cardinal directions (e.g. "North-East")
-         * because it is not clear which normalization policy would match common usage.
-         */
         @Override
+        public Unit<?> getUnitReplacement(final CoordinateSystemAxis axis, final Unit<?> unit) {
+            return unit;
+        }
+
+        @Override
+        @Deprecated
         public AxisDirection getDirectionReplacement(final AxisDirection direction) {
-            return AxisDirections.isIntercardinal(direction) ? direction : AxisDirections.absolute(direction);
+            return getDirectionReplacement(null, direction);
+        }
+
+        @Override
+        public AxisDirection getDirectionReplacement(CoordinateSystemAxis axis, AxisDirection direction) {
+            return NORMALIZED.getDirectionReplacement(axis, direction);
         }
     },
 
@@ -275,14 +306,26 @@ public enum AxesConvention implements Ax
         }
 
         @Override
+        @Deprecated
         public Unit<?> getUnitReplacement(final Unit<?> unit) {
             return unit;
         }
 
         @Override
+        public Unit<?> getUnitReplacement(CoordinateSystemAxis axis, final Unit<?> unit) {
+            return unit;
+        }
+
+        @Override
+        @Deprecated
         public AxisDirection getDirectionReplacement(final AxisDirection direction) {
             return direction;
         }
+
+        @Override
+        public AxisDirection getDirectionReplacement(CoordinateSystemAxis axis, final AxisDirection direction) {
+            return direction;
+        }
     },
 
     /**
@@ -315,13 +358,25 @@ public enum AxesConvention implements Ax
         }
 
         @Override
+        @Deprecated
         public Unit<?> getUnitReplacement(final Unit<?> unit) {
             return unit;
         }
 
         @Override
+        public Unit<?> getUnitReplacement(CoordinateSystemAxis axis, final Unit<?> unit) {
+            return unit;
+        }
+
+        @Override
+        @Deprecated
         public AxisDirection getDirectionReplacement(final AxisDirection direction) {
             return direction;
         }
+
+        @Override
+        public AxisDirection getDirectionReplacement(CoordinateSystemAxis axis, final AxisDirection direction) {
+            return direction;
+        }
     }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxisFilter.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxisFilter.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxisFilter.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxisFilter.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -42,7 +42,7 @@ import javax.measure.unit.Unit;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.6
- * @version 0.6
+ * @version 0.7
  * @module
  *
  * @see CoordinateSystems#replaceAxes(CoordinateSystem, AxisFilter)
@@ -65,7 +65,7 @@ public interface AxisFilter {
      *
      * {@preformat java
      *     &#64;Override
-     *     public getDirectionReplacement(AxisDirection direction) {
+     *     public getDirectionReplacement(CoordinateSystemAxis axis, AxisDirection direction) {
      *         if (direction == AxisDirection.DOWN) {
      *             direction = AxisDirection.UP;
      *         }
@@ -74,9 +74,21 @@ public interface AxisFilter {
      * }
      * </div>
      *
+     * @param  axis The axis for which to change axis direction, if desired.
+     * @param  direction The original axis direction.
+     * @return The new axis direction, or {@code direction} if there is no change.
+     *
+     * @since 0.7
+     */
+    AxisDirection getDirectionReplacement(CoordinateSystemAxis axis, AxisDirection direction);
+
+    /**
+     * @deprecated Use {@link #getDirectionReplacement(CoordinateSystemAxis, AxisDirection)} instead.
+     *
      * @param  direction The original axis direction.
      * @return The new axis direction, or {@code direction} if there is no change.
      */
+    @Deprecated
     AxisDirection getDirectionReplacement(AxisDirection direction);
 
     /**
@@ -88,7 +100,7 @@ public interface AxisFilter {
      *
      * {@preformat java
      *     &#64;Override
-     *     public Unit<?> getUnitReplacement(Unit<?> unit) {
+     *     public Unit<?> getUnitReplacement(CoordinateSystemAxis axis, Unit<?> unit) {
      *         if (Units.isAngular(unit)) {
      *             unit = NonSI.DEGREE_ANGLE;
      *         }
@@ -97,8 +109,20 @@ public interface AxisFilter {
      * }
      * </div>
      *
+     * @param  axis The axis for which to change unit, if desired.
+     * @param  unit The original axis unit.
+     * @return The new axis unit, or {@code unit} if there is no change.
+     *
+     * @since 0.7
+     */
+    Unit<?> getUnitReplacement(CoordinateSystemAxis axis, Unit<?> unit);
+
+    /**
+     * @deprecated Use {@link #getUnitReplacement(CoordinateSystemAxis, Unit)} instead.
+     *
      * @param  unit The original axis unit.
      * @return The new axis unit, or {@code unit} if there is no change.
      */
+    @Deprecated
     Unit<?> getUnitReplacement(Unit<?> unit);
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -349,7 +349,7 @@ public final class CoordinateSystems ext
      *     CoordinateSystem cs = ...;
      *     cs = CoordinateSystems.replaceAxes(cs, new AxisFilter() {
      *         &#64;Override
-     *         public Unit<?> getUnitReplacement(Unit<?> unit) {
+     *         public Unit<?> getUnitReplacement(CoordinateSystemAxis axis, Unit<?> unit) {
      *             if (Units.isAngular(unit)) {
      *                 unit = NonSI.DEGREE_ANGLE;
      *             }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -323,7 +323,7 @@ public class DefaultCoordinateSystemAxis
                 } else if (dir.equals(AxisDirection.EAST)) {
                     min = fromDegrees.convert(Longitude.MIN_VALUE);
                     max = fromDegrees.convert(Longitude.MAX_VALUE);
-                    rm  = RangeMeaning.WRAPAROUND; // 180°E wraps to 180°W
+                    rm  = RangeMeaning.WRAPAROUND;                                  // 180°E wraps to 180°W
                 }
                 if (min > max) {
                     final double t = min;
@@ -697,10 +697,10 @@ public class DefaultCoordinateSystemAxis
      */
     private static CoordinateSystem getEnclosingCS(final Formatter formatter) {
         final FormattableObject e = formatter.getEnclosingElement(1);
-        if (e instanceof CoordinateReferenceSystem) {   // This is what we expect in standard WKT.
+        if (e instanceof CoordinateReferenceSystem) {           // This is what we expect in standard WKT.
             return ((CoordinateReferenceSystem) e).getCoordinateSystem();
         }
-        if (e instanceof CoordinateSystem) {    // Not standard WKT, but conceptually the right thing.
+        if (e instanceof CoordinateSystem) {                    // Not standard WKT, but conceptually the right thing.
             return (CoordinateSystem) e;
         }
         return null;

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -165,7 +165,7 @@ public class DefaultCylindricalCS extend
         if (!AxisDirections.isSpatialOrUserDefined(direction, false)) {
             return INVALID_DIRECTION;
         }
-        if (!Units.isLinear(unit)) {
+        if (!Units.isAngular(unit) && !Units.isLinear(unit)) {
             return INVALID_UNIT;
         }
         return VALID;

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -157,7 +157,7 @@ public class DefaultLinearCS extends Abs
         if (!AxisDirections.isSpatialOrUserDefined(direction, false)) {
             return INVALID_DIRECTION;
         }
-        if (!Units.isLinear(unit)) {
+        if (!Units.isLinear(unit) && !Unit.ONE.equals(unit)) {
             return INVALID_UNIT;
         }
         return VALID;

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -26,11 +26,16 @@ import org.opengis.referencing.cs.RangeM
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.cs.EllipsoidalCS;
+import org.opengis.referencing.cs.SphericalCS;
+import org.opengis.referencing.cs.CylindricalCS;
+import org.opengis.referencing.cs.PolarCS;
 import org.apache.sis.internal.metadata.AxisDirections;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.measure.Units;
 
 import static java.util.Collections.singletonMap;
 import static org.opengis.referencing.IdentifiedObject.NAME_KEY;
@@ -50,12 +55,39 @@ import static org.apache.sis.internal.re
  * The main usage for this class is to reorder the axes in some fixed order like
  * (<var>x</var>, <var>y</var>, <var>z</var>) or (<var>longitude</var>, <var>latitude</var>).
  *
+ * <p>The normalization performed by this class shall be compatible with axis order expected by various
+ * {@code MathTransform} implementations in the {@link org.apache.sis.referencing.operation.transform} package.
+ * In particular:</p>
+ *
+ * <ul>
+ *   <li>{@code EllipsoidToCentricTransform} input:<ol>
+ *     <li>Geodetic longitude (λ) in degrees</li>
+ *     <li>Geodetic latitude (φ) in degrees</li>
+ *     <li>Height in units of semi-axes</li>
+ *   </ol></li>
+ *   <li>{@code SphericalToCartesian} input:<ol>
+ *     <li>Spherical longitude in degrees</li>
+ *     <li>Spherical latitude in degrees</li>
+ *     <li>Spherical radius (r) in any units</li>
+ *   </ol></li>
+ *   <li>{@code CartesianToSpherical} input:<ol>
+ *     <li>X in units of the above radius</li>
+ *     <li>Y in units of the above radius</li>
+ *     <li>Z in units of the above radius</li>
+ *   </ol></li>
+ *   <li>{@code CylindricalToCartesian} input:<ol>
+ *     <li>Radius (r) in any units</li>
+ *     <li>Angle (θ) in degrees</li>
+ *     <li>Height (z) in any units</li>
+ *   </ol></li>
+ * </ul>
+ *
  * <p>This class implements {@link Comparable} for opportunist reasons.
  * This should be considered as an implementation details.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.5
+ * @version 0.7
  * @module
  */
 final class Normalizer implements Comparable<Normalizer> {
@@ -67,6 +99,30 @@ final class Normalizer implements Compar
     };
 
     /**
+     * Number of bits by which to shift the {@link AxisDirection#ordinal()} value in order to make room for
+     * inserting intermediate values between them. A shift of 2 make room for {@literal 1 << 2} intermediate
+     * values. Those intermediate values are declared in the {@link #ORDER} map.
+     *
+     * @see #order(AxisDirection)
+     */
+    private static final int SHIFT = 2;
+
+    /**
+     * Custom code list values to handle as if the where defined between two GeoAPI values.
+     *
+     * @see #order(AxisDirection)
+     */
+    private static final Map<AxisDirection,Integer> ORDER = new HashMap<AxisDirection,Integer>();
+    static {
+        final Map<AxisDirection,Integer> m = ORDER;
+        // Get ordinal of last compass direction defined by GeoAPI. We will continue on the horizontal plane.
+        final int horizontal = (AxisDirection.NORTH.ordinal() + (AxisDirections.COMPASS_COUNT - 1)) << SHIFT;
+        m.put(AxisDirections.AWAY_FROM,         horizontal + 1);
+        m.put(AxisDirections.COUNTER_CLOCKWISE, horizontal + 2);
+        m.put(AxisDirections.CLOCKWISE,         horizontal + 3);
+    }
+
+    /**
      * The axis to be compared by {@link #compareTo(Normalizer)}.
      */
     private final CoordinateSystemAxis axis;
@@ -78,53 +134,78 @@ final class Normalizer implements Compar
     private final DirectionAlongMeridian meridian;
 
     /**
+     * Angular units order relative to other units.
+     * A value of -1 means that angular units should be first.
+     * A value of +1 means than angular units should be last.
+     * A value of 0 means to not use this criterion.
+     */
+    private final int unitOrder;
+
+    /**
      * For internal usage by {@link #sort(CoordinateSystemAxis[])} only.
      */
-    private Normalizer(final CoordinateSystemAxis axis) {
+    private Normalizer(final CoordinateSystemAxis axis, final int angularUnitOrder) {
         this.axis = axis;
+        unitOrder = Units.isAngular(axis.getUnit()) ? angularUnitOrder : 0;
         final AxisDirection dir = axis.getDirection();
         meridian = AxisDirections.isUserDefined(dir) ? DirectionAlongMeridian.parse(dir) : null;
     }
 
     /**
+     * Returns the order of the given axis direction.
+     */
+    private static int order(final AxisDirection dir) {
+        final Integer p = ORDER.get(dir);
+        return (p != null) ? p : (dir.ordinal() << SHIFT);
+    }
+
+    /**
      * Compares two axis for an order that try to favor right-handed coordinate systems.
      * Compass directions like North and East are first. Vertical directions like Up or Down are next.
      */
     @Override
     public int compareTo(final Normalizer that) {
-        final AxisDirection d1 = this.axis.getDirection();
-        final AxisDirection d2 = that.axis.getDirection();
-        final int compass = AxisDirections.angleForCompass(d2, d1);
-        if (compass != Integer.MIN_VALUE) {
-            return compass;
-        }
-        if (meridian != null) {
-            if (that.meridian != null) {
-                return meridian.compareTo(that.meridian);
+        int d = unitOrder - that.unitOrder;
+        if (d == 0) {
+            final AxisDirection d1 = this.axis.getDirection();
+            final AxisDirection d2 = that.axis.getDirection();
+            d = AxisDirections.angleForCompass(d2, d1);
+            if (d == Integer.MIN_VALUE) {
+                if (meridian != null) {
+                    if (that.meridian != null) {
+                        d = meridian.compareTo(that.meridian);
+                    } else {
+                        d = -1;
+                    }
+                } else if (that.meridian != null) {
+                    d = +1;
+                } else {
+                    d = order(d1) - order(d2);
+                }
             }
-            return -1;
-        } else if (that.meridian != null) {
-            return +1;
         }
-        return d1.ordinal() - d2.ordinal();
+        return d;
     }
 
     /**
      * Sorts the specified axis in an attempt to create a right-handed system.
      * The sorting is performed in place. This method returns {@code true} if
      * at least one axis moved as result of this method call.
+     *
+     * @param axes The axes to sort.
+     * @param angularUnitOrder -1 for sorting angular units first, +1 for sorting them last, or 0 if neutral.
      */
-    static boolean sort(final CoordinateSystemAxis[] axis) {
-        final Normalizer[] wrappers = new Normalizer[axis.length];
-        for (int i=0; i<axis.length; i++) {
-            wrappers[i] = new Normalizer(axis[i]);
+    static boolean sort(final CoordinateSystemAxis[] axes, final int angularUnitOrder) {
+        final Normalizer[] wrappers = new Normalizer[axes.length];
+        for (int i=0; i<axes.length; i++) {
+            wrappers[i] = new Normalizer(axes[i], angularUnitOrder);
         }
         Arrays.sort(wrappers);
         boolean changed = false;
-        for (int i=0; i<axis.length; i++) {
+        for (int i=0; i<axes.length; i++) {
             final CoordinateSystemAxis a = wrappers[i].axis;
-            changed |= (axis[i] != a);
-            axis[i] = a;
+            changed |= (axes[i] != a);
+            axes[i] = a;
         }
         return changed;
     }
@@ -140,8 +221,8 @@ final class Normalizer implements Compar
     static CoordinateSystemAxis normalize(final CoordinateSystemAxis axis, final AxisFilter changes) {
         final Unit<?>       unit      = axis.getUnit();
         final AxisDirection direction = axis.getDirection();
-        final Unit<?>       newUnit   = changes.getUnitReplacement(unit);
-        final AxisDirection newDir    = changes.getDirectionReplacement(direction);
+        final Unit<?>       newUnit   = changes.getUnitReplacement(axis, unit);
+        final AxisDirection newDir    = changes.getDirectionReplacement(axis, direction);
         /*
          * Reuse some properties (name, remarks, etc.) from the existing axis. If the direction changed,
          * then the axis name may need change too (e.g. "Westing" → "Easting"). The new axis name may be
@@ -153,22 +234,8 @@ final class Normalizer implements Compar
             return axis;
         }
         final String abbreviation = axis.getAbbreviation();
-        String newAbbr = abbreviation;
-        if (!sameDirection) {
-            if (AxisDirections.isCompass(direction)) {
-                if (CharSequences.isAcronymForWords(abbreviation, direction.name())) {
-                    if (newDir.equals(AxisDirection.EAST)) {
-                        newAbbr = "E";
-                    } else if (newDir.equals(AxisDirection.NORTH)) {
-                        newAbbr = "N";
-                    }
-                }
-            } else if (newDir.equals(AxisDirection.UP)) {
-                newAbbr = "z";
-            } else if (newDir.equals(AxisDirection.FUTURE)) {
-                newAbbr = "t";
-            }
-        }
+        final String newAbbr = sameDirection ? abbreviation :
+                AxisDirections.suggestAbbreviation(axis.getName().getCode(), newDir, newUnit);
         final Map<String,Object> properties = new HashMap<String,Object>();
         if (newAbbr.equals(abbreviation)) {
             properties.putAll(IdentifiedObjects.getProperties(axis, EXCLUDES));
@@ -176,18 +243,30 @@ final class Normalizer implements Compar
             properties.put(NAME_KEY, UNNAMED);
         }
         /*
-         * Converts the axis range and build the new axis.
+         * Convert the axis range and build the new axis. The axis range will be converted only if
+         * the axis direction is the same or the opposite, otherwise we do not know what should be
+         * the new values. In the particular case of opposite axis direction, we need to reverse the
+         * sign of minimum and maximum values.
          */
-        final UnitConverter c;
-        try {
-            c = unit.getConverterToAny(newUnit);
-        } catch (ConversionException e) {
-            // Use IllegalStateException because the public API is an AbstractCS member method.
-            throw new IllegalStateException(Errors.format(Errors.Keys.IllegalUnitFor_2, "axis", unit), e);
-        }
-        properties.put(DefaultCoordinateSystemAxis.MINIMUM_VALUE_KEY, c.convert(axis.getMinimumValue()));
-        properties.put(DefaultCoordinateSystemAxis.MAXIMUM_VALUE_KEY, c.convert(axis.getMaximumValue()));
-        properties.put(DefaultCoordinateSystemAxis.RANGE_MEANING_KEY, axis.getRangeMeaning());
+        if (sameDirection || newDir.equals(AxisDirections.opposite(direction))) {
+            final UnitConverter c;
+            try {
+                c = unit.getConverterToAny(newUnit);
+            } catch (ConversionException e) {
+                // Use IllegalStateException because the public API is an AbstractCS member method.
+                throw new IllegalStateException(Errors.format(Errors.Keys.IllegalUnitFor_2, "axis", unit), e);
+            }
+            double minimum = c.convert(axis.getMinimumValue());
+            double maximum = c.convert(axis.getMaximumValue());
+            if (!sameDirection) {
+                final double tmp = minimum;
+                minimum = -maximum;
+                maximum = -tmp;
+            }
+            properties.put(DefaultCoordinateSystemAxis.MINIMUM_VALUE_KEY, minimum);
+            properties.put(DefaultCoordinateSystemAxis.MAXIMUM_VALUE_KEY, maximum);
+            properties.put(DefaultCoordinateSystemAxis.RANGE_MEANING_KEY, axis.getRangeMeaning());
+        }
         return new DefaultCoordinateSystemAxis(properties, newAbbr, newDir, newUnit);
     }
 
@@ -221,7 +300,26 @@ final class Normalizer implements Compar
          * If nothing changed, return the given Coordinate System as-is.
          */
         if (reorder) {
-            changed |= sort(axes);
+            int angularUnitOrder = 0;
+            if  (cs instanceof EllipsoidalCS || cs instanceof SphericalCS) angularUnitOrder = -1;      // (λ,φ,h) order
+            else if (cs instanceof CylindricalCS || cs instanceof PolarCS) angularUnitOrder = +1;      // (r,θ) order
+            changed |= sort(axes, angularUnitOrder);
+            if (angularUnitOrder == 1) {                            // Cylindrical or polar
+                /*
+                 * Change (r,z,θ) to (r,θ,z) order in CylindricalCS. The check on unit of
+                 * measurements should be always true, but we verify as a paranoiac check.
+                 */
+                if (axes.length == 3 && isLengthAndAngle(axes, 1)) {
+                    ArraysExt.swap(axes, 1, 2);
+                }
+                /*
+                 * If we were not allowed to normalize the axis direction, we may have a
+                 * left-handed coordinate system here. If so, make it right-handed.
+                 */
+                if (AxisDirections.CLOCKWISE.equals(axes[1].getDirection()) && isLengthAndAngle(axes, 0)) {
+                    ArraysExt.swap(axes, 0, 1);
+                }
+            }
         }
         if (!changed && n == dimension) {
             return null;
@@ -236,6 +334,14 @@ final class Normalizer implements Compar
     }
 
     /**
+     * Returns {@code true} if the units of measurement at the given position is a linear unit,
+     * followed by an angular unit on the next axis.
+     */
+    private static boolean isLengthAndAngle(final CoordinateSystemAxis[] axes, final int p) {
+        return Units.isLinear(axes[p].getUnit()) && Units.isAngular(axes[p+1].getUnit());
+    }
+
+    /**
      * Returns a coordinate system with the same axes than the given CS, except that the wrapround axes
      * are shifted to a range of positive values. This method can be used in order to shift between the
      * [-180 … +180]° and [0 … 360]° ranges of longitude values.

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -21,11 +21,13 @@ import java.util.Arrays;
 import java.io.Serializable;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.referencing.datum.GeodeticDatum;
+import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.operation.Matrix;
 import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.io.wkt.FormattableObject;
 import org.apache.sis.io.wkt.Formatter;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.internal.metadata.WKTKeywords;
@@ -123,7 +125,7 @@ import org.apache.sis.internal.jdk7.Obje
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.6
+ * @version 0.7
  * @module
  *
  * @see DefaultGeodeticDatum#getBursaWolfParameters()
@@ -226,15 +228,37 @@ public class BursaWolfParameters extends
 
     /**
      * Verifies parameters validity after initialization of {@link DefaultGeodeticDatum}.
-     */
-    void verify() {
+     * This method requires that the prime meridian of the target datum is either the same
+     * than the enclosing {@code GeodeticDatum}, or Greenwich. We put this restriction for
+     * avoiding ambiguity about whether the longitude rotation should be applied before or
+     * after the datum shift.
+     *
+     * <p>If the target prime meridian is Greenwich, then SIS will assume that the datum shift
+     * needs to be applied in a coordinate system having Greenwich as the prime meridian.</p>
+     *
+     * <p><b>Maintenance note:</b>
+     * if the above policy regarding prime meridians is modified, then some {@code createOperationStep(…)} method
+     * implementations in {@link org.apache.sis.referencing.operation.CoordinateOperationInference} may need to be
+     * revisited. See especially the methods creating a transformation between a pair of {@code GeocentricCRS} or
+     * between a pair of {@code GeographicCRS} (tip: search for {@code DefaultGeodeticDatum}).</p>
+     *
+     * @param pm The prime meridian of the enclosing {@code GeodeticDatum}.
+     */
+    void verify(final PrimeMeridian pm) throws IllegalArgumentException {
+        if (targetDatum != null) {
+            final PrimeMeridian actual = targetDatum.getPrimeMeridian();
+            if (actual.getGreenwichLongitude() != 0 && !Utilities.equalsIgnoreMetadata(pm, actual)) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedPrimeMeridian_2,
+                        IdentifiedObjects.getName(pm, null), IdentifiedObjects.getName(actual, null)));
+            }
+        }
         ensureFinite("tX", tX);
         ensureFinite("tY", tY);
         ensureFinite("tZ", tZ);
         ensureFinite("rX", rX);
         ensureFinite("rY", rY);
         ensureFinite("rZ", rZ);
-        ensureBetween("dS", -PPM, PPM, dS); // For preventing zero or negative value on the matrix diagonal.
+        ensureBetween("dS", -PPM, PPM, dS);     // For preventing zero or negative value on the matrix diagonal.
     }
 
     /**
@@ -276,7 +300,7 @@ public class BursaWolfParameters extends
     public double[] getValues() {
         final double[] elements = new double[(dS != 0) ? 7 : (rZ != 0 || rY != 0 || rX != 0) ? 6 : 3];
         switch (elements.length) {
-            default: elements[6] = dS;  // Fallthrough everywhere.
+            default: elements[6] = dS;      // Fallthrough everywhere.
             case 6:  elements[5] = rZ;
                      elements[4] = rY;
                      elements[3] = rX;
@@ -307,7 +331,7 @@ public class BursaWolfParameters extends
     @SuppressWarnings("fallthrough")
     public void setValues(final double... elements) {
         switch (elements.length) {
-            default: dS = elements[6];  // Fallthrough everywhere.
+            default: dS = elements[6];      // Fallthrough everywhere.
             case 6:  rZ = elements[5];
             case 5:  rY = elements[4];
             case 4:  rX = elements[3];
@@ -510,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
@@ -543,17 +571,17 @@ public class BursaWolfParameters extends
             }
             for (int i = j+1; i < SIZE-1; i++) {
                 S.setFrom(RS);
-                S.inverseDivide(getNumber(matrix, j,i)); // Negative rotation term.
+                S.inverseDivide(getNumber(matrix, j,i));        // Negative rotation term.
                 double value = S.value;
                 double error = S.error;
                 S.setFrom(RS);
-                S.inverseDivide(getNumber(matrix, i,j)); // Positive rotation term.
-                if (!(abs(value + S.value) <= tolerance)) { // We expect r1 ≈ -r2
+                S.inverseDivide(getNumber(matrix, i,j));        // Positive rotation term.
+                if (!(abs(value + S.value) <= tolerance)) {     // We expect r1 ≈ -r2
                     throw new IllegalArgumentException(Errors.format(Errors.Keys.NotASkewSymmetricMatrix));
                 }
                 S.subtract(value, error);
                 S.multiply(0.5, 0);
-                value = S.value; // Average of the two rotation terms.
+                value = S.value;                                // Average of the two rotation terms.
                 switch (j*SIZE + i) {
                     case 1: rZ =  value; break;
                     case 2: rY = -value; break;

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java?rev=1737107&r1=1737106&r2=1737107&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java [UTF-8] Wed Mar 30 12:35:10 2016
@@ -243,6 +243,13 @@ public class DefaultGeodeticDatum extend
      *   </tr>
      * </table>
      *
+     * If Bursa-Wolf parameters are specified, then the prime meridian of their
+     * {@linkplain BursaWolfParameters#getTargetDatum() target datum} shall be either the same than the
+     * {@code primeMeridian} given to this constructor, or Greenwich. This restriction is for avoiding
+     * ambiguity about whether the longitude rotation shall be applied before or after the datum shift.
+     * If the target prime meridian is Greenwich, then the datum shift will be applied in a coordinate
+     * system having Greenwich as the prime meridian.
+     *
      * @param properties    The properties to be given to the identified object.
      * @param ellipsoid     The ellipsoid.
      * @param primeMeridian The prime meridian.
@@ -265,7 +272,7 @@ public class DefaultGeodeticDatum extend
                 BursaWolfParameters param = bursaWolf[i];
                 ensureNonNullElement("bursaWolf", i, param);
                 param = param.clone();
-                param.verify();
+                param.verify(primeMeridian);
                 bursaWolf[i] = param;
             }
         }
@@ -368,12 +375,21 @@ public class DefaultGeodeticDatum extend
      * 1033 – <cite>Position Vector transformation (geocentric domain)</cite>, or
      * 1053 – <cite>Time-dependent Position Vector transformation</cite>.
      *
-     * <p>If this datum and the given {@code targetDatum} do not use the same
-     * {@linkplain #getPrimeMeridian() prime meridian}, then it is caller's responsibility
-     * to apply longitude rotation before to use the matrix returned by this method.</p>
-     *
-     * <div class="section">Search order</div>
-     * This method performs the search in the following order:
+     * <p>If this datum and the given {@code targetDatum} do not use the same {@linkplain #getPrimeMeridian() prime meridian},
+     * then it is caller's responsibility to to apply longitude rotation before to use the matrix returned by this method.
+     * The target prime meridian should be Greenwich (see {@linkplain #DefaultGeodeticDatum(Map, Ellipsoid, PrimeMeridian)
+     * constructor javadoc}), in which case the datum shift should be applied in a geocentric coordinate system having
+     * Greenwich as the prime meridian.</p>
+     *
+     * <div class="note"><b>Note:</b>
+     * in EPSG dataset version 8.9, all datum shifts that can be represented by this method use Greenwich as the
+     * prime meridian, both in source and target datum.</div>
+     *
+     * <div class="section">Search criterion</div>
+     * If the given {@code areaOfInterest} is non-null and contains at least one geographic bounding box, then this
+     * method ignores any Bursa-Wolf parameters having a {@linkplain BursaWolfParameters#getDomainOfValidity() domain
+     * of validity} that does not intersect the given geographic extent.
+     * This method performs the search among the remaining parameters in the following order:
      * <ol>
      *   <li>If this {@code GeodeticDatum} contains {@code BursaWolfParameters} having the given
      *       {@linkplain BursaWolfParameters#getTargetDatum() target datum} (ignoring metadata),
@@ -449,7 +465,7 @@ public class DefaultGeodeticDatum extend
          * not a subclass of BursaWolfParameters. This optimisation covers the vast majority of cases.
          */
         return bursaWolf.getPositionVectorTransformation(bursaWolf.getClass() != BursaWolfParameters.class ?
-                Extents.getDate(areaOfInterest, 0.5) : null); // 0.5 is for choosing midway instant.
+                Extents.getDate(areaOfInterest, 0.5) : null);       // 0.5 is for choosing midway instant.
     }
 
     /**
@@ -530,7 +546,7 @@ public class DefaultGeodeticDatum extend
     @Override
     public boolean equals(final Object object, final ComparisonMode mode) {
         if (object == this) {
-            return true; // Slight optimization.
+            return true;                                // Slight optimization.
         }
         if (!super.equals(object, mode)) {
             return false;
@@ -547,14 +563,10 @@ public class DefaultGeodeticDatum extend
                 return deepEquals(getEllipsoid(),     that.getEllipsoid(),     mode) &&
                        deepEquals(getPrimeMeridian(), that.getPrimeMeridian(), mode);
                 /*
-                 * HACK: We do not consider Bursa-Wolf parameters as a non-metadata field.
-                 *       This is needed in order to get equalsIgnoreMetadata(...) to returns
-                 *       'true' when comparing the WGS84 constant in this class with a WKT
-                 *       DATUM element with a TOWGS84[0,0,0,0,0,0,0] element. Furthermore,
-                 *       the Bursa-Wolf parameters are not part of ISO 19111 specification.
-                 *       We don't want two CRS to be considered as different because one has
-                 *       more of those transformation informations (which is nice, but doesn't
-                 *       change the CRS itself).
+                 * Bursa-Wolf parameters are considered ignorable metadata. This is needed in order to get
+                 * equalsIgnoreMetadata(…) to return true when comparing WGS84 datums with and without the
+                 * WKT 1 "TOWGS84[0,0,0,0,0,0,0]" element. Furthermore those Bursa-Wolf parameters are not
+                 * part of ISO 19111 specification.
                  */
             }
         }