sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1817597 [5/19] - in /sis/branches/ISO-19115-3: ./ application/ application/sis-console/ application/sis-console/src/main/artifact/ application/sis-console/src/main/artifact/lib/ application/sis-console/src/main/artifact/lib/darwin/ applica...
Date Sat, 09 Dec 2017 10:57:47 GMT
Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -21,10 +21,10 @@ package org.apache.sis.geometry;
  * support Java2D (e.g. Android),  or applications that do not need it may want to avoid to
  * force installation of the Java2D module (e.g. JavaFX/SWT).
  */
+import java.util.Set;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.referencing.cs.RangeMeaning;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -40,6 +40,7 @@ import org.apache.sis.util.ComparisonMod
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
 import org.apache.sis.referencing.operation.transform.AbstractMathTransform;
 import org.apache.sis.internal.referencing.CoordinateOperations;
 import org.apache.sis.internal.referencing.DirectPositionView;
@@ -86,7 +87,7 @@ import static org.apache.sis.util.String
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 0.5
+ * @version 0.8
  *
  * @see org.apache.sis.metadata.iso.extent.Extents
  * @see CRS
@@ -102,13 +103,6 @@ public final class Envelopes extends Sta
     }
 
     /**
-     * Returns {@code true} if the given axis is of kind "Wrap Around".
-     */
-    static boolean isWrapAround(final CoordinateSystemAxis axis) {
-        return RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning());
-    }
-
-    /**
      * Invoked when a recoverable exception occurred. Those exceptions must be minor enough
      * that they can be silently ignored in most cases.
      */
@@ -444,6 +438,7 @@ public final class Envelopes extends Sta
         if (envelope == null) {
             return null;
         }
+        boolean isOperationComplete = true;
         final CoordinateReferenceSystem sourceCRS = operation.getSourceCRS();
         if (sourceCRS != null) {
             final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
@@ -463,6 +458,7 @@ public final class Envelopes extends Sta
                     throw new TransformException(Errors.format(Errors.Keys.CanNotTransformEnvelope), e);
                 }
                 if (!mt.isIdentity()) {
+                    isOperationComplete = false;
                     envelope = transform(mt, envelope);
                 }
             }
@@ -573,7 +569,7 @@ public final class Envelopes extends Sta
         long isWrapAroundAxis = 0;
         long dimensionBitMask = 1;
         final int dimension = targetCS.getDimension();
-        for (int i=0; i<dimension; i++, dimensionBitMask <<= 1) {
+poles:  for (int i=0; i<dimension; i++, dimensionBitMask <<= 1) {
             final CoordinateSystemAxis axis = targetCS.getAxis(i);
             if (axis == null) {                 // Should never be null, but check as a paranoiac safety.
                 continue;
@@ -605,9 +601,9 @@ public final class Envelopes extends Sta
                          * lost dimensions. So we don't log any warning in this case.
                          */
                         if (dimension >= mt.getSourceDimensions()) {
-                            recoverableException(Envelopes.class, exception);
+                            warning = exception;
                         }
-                        return transformed;
+                        break poles;
                     }
                     targetPt = new GeneralDirectPosition(mt.getSourceDimensions());
                     for (int j=0; j<dimension; j++) {
@@ -644,7 +640,7 @@ public final class Envelopes extends Sta
              * axis have been included in the envelope  (in which case the next step after this
              * loop doesn't need to be executed for that axis).
              */
-            if ((includedMinValue & includedMaxValue & dimensionBitMask) == 0 && isWrapAround(axis)) {
+            if ((includedMinValue & includedMaxValue & dimensionBitMask) == 0 && CoordinateOperations.isWrapAround(axis)) {
                 isWrapAroundAxis |= dimensionBitMask;
             }
             // Restore 'targetPt' to its initial state, which is equals to 'centerPt'.
@@ -664,7 +660,7 @@ public final class Envelopes extends Sta
             while (isWrapAroundAxis != 0) {
                 final int wrapAroundDimension = Long.numberOfTrailingZeros(isWrapAroundAxis);
                 dimensionBitMask = 1 << wrapAroundDimension;
-                isWrapAroundAxis &= ~dimensionBitMask; // Clear now the bit, for the next iteration.
+                isWrapAroundAxis &= ~dimensionBitMask;              // Clear now the bit, for the next iteration.
                 final CoordinateSystemAxis wrapAroundAxis = targetCS.getAxis(wrapAroundDimension);
                 final double min = wrapAroundAxis.getMinimumValue();
                 final double max = wrapAroundAxis.getMaximumValue();
@@ -687,8 +683,8 @@ public final class Envelopes extends Sta
                     for (int c=0; c<4; c++) {
                         /*
                          * Set the ordinate value along the axis having the singularity point
-                         * (cases c=0 and c=2). If the envelope did not included that point,
-                         * then skip completly this case and the next one, i.e. skip c={0,1}
+                         * (cases c=0 and c=2).  If the envelope did not included that point,
+                         * then skip completely this case and the next one, i.e. skip c={0,1}
                          * or skip c={2,3}.
                          */
                         double value = max;
@@ -720,6 +716,21 @@ public final class Envelopes extends Sta
                 targetPt.setOrdinate(wrapAroundDimension, centerPt[wrapAroundDimension]);
             }
         }
+        /*
+         * At this point we finished envelope transformation. Verify if some ordinates need to be "wrapped around"
+         * as a result of the coordinate operation.  This is usually the longitude axis where the source CRS uses
+         * the [-180 … +180]° range and the target CRS uses the [0 … 360]° range, or the converse. We do not wrap
+         * around if the source and target axes use the same range (e.g. the longitude stay [-180 … +180]°) in order
+         * to reduce the risk of discontinuities. If the user really wants unconditional wrap around, (s)he can call
+         * GeneralEnvelope.normalize().
+         */
+        final Set<Integer> wrapAroundChanges;
+        if (isOperationComplete && operation instanceof AbstractCoordinateOperation) {
+            wrapAroundChanges = ((AbstractCoordinateOperation) operation).getWrapAroundChanges();
+        } else {
+            wrapAroundChanges = CoordinateOperations.wrapAroundChanges(sourceCRS, targetCS);
+        }
+        transformed.normalize(targetCS, 0, wrapAroundChanges.size(), wrapAroundChanges.iterator());
         if (warning != null) {
             recoverableException(Envelopes.class, warning);
         }
@@ -734,7 +745,7 @@ public final class Envelopes extends Sta
      *
      * <p>Example:</p>
      * <ul>
-     *   <li>{@code BOX(-180 -90, 180 90)} (not really a geometry, but understood by many softwares)</li>
+     *   <li>{@code BOX(-180 -90, 180 90)} (not really a geometry, but understood by many software products)</li>
      *   <li>{@code POINT(6 10)}</li>
      *   <li>{@code MULTIPOLYGON(((1 1, 5 1, 1 5, 1 1),(2 2, 3 2, 3 3, 2 2)))}</li>
      *   <li>{@code GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(3 8,7 10))}</li>
@@ -771,7 +782,7 @@ public final class Envelopes extends Sta
      *
      * <div class="note"><b>Note:</b>
      * The {@code BOX} element is not part of the standard <cite>Well Known Text</cite> (WKT) format.
-     * However it is understood by many softwares, for example GDAL and PostGIS.</div>
+     * However it is understood by many software libraries, for example GDAL and PostGIS.</div>
      *
      * The string returned by this method can be {@linkplain GeneralEnvelope#GeneralEnvelope(CharSequence)
      * parsed} by the {@code GeneralEnvelope} constructor.

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -22,6 +22,7 @@ package org.apache.sis.geometry;
  * force installation of the Java2D module (e.g. JavaFX/SWT).
  */
 import java.util.Arrays;
+import java.util.Iterator;
 import java.io.Serializable;
 import java.lang.reflect.Field;
 import org.opengis.referencing.cs.RangeMeaning;
@@ -36,8 +37,9 @@ import org.opengis.metadata.extent.Geogr
 import org.apache.sis.util.resources.Errors;
 
 import static org.apache.sis.util.ArgumentChecks.*;
-import static org.apache.sis.math.MathFunctions.isNegative;
 import static org.apache.sis.math.MathFunctions.isSameSign;
+import static org.apache.sis.math.MathFunctions.isNegative;
+import static org.apache.sis.math.MathFunctions.isNegativeZero;
 
 
 /**
@@ -111,7 +113,7 @@ import static org.apache.sis.math.MathFu
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 0.5
+ * @version 0.8
  *
  * @see Envelope2D
  * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox
@@ -807,10 +809,10 @@ public class GeneralEnvelope extends Arr
                          */
                         final double min, max;
                         final double csSpan = getSpan(getAxis(crs, i));
-                        if (span1 >= csSpan) {
+                        if (span1 >= csSpan || isNegativeZero(span1)) {       // Negative zero if [+0 … -0] range.
                             min = min0;
                             max = max0;
-                        } else if (span0 >= csSpan) {
+                        } else if (span0 >= csSpan || isNegativeZero(span0)) {
                             min = min1;
                             max = max1;
                         } else {
@@ -887,53 +889,68 @@ public class GeneralEnvelope extends Arr
      * @see AbstractDirectPosition#normalize()
      */
     public boolean normalize() {
+        if (crs == null) {
+            return false;
+        }
+        final int beginIndex = beginIndex();
+        return normalize(crs.getCoordinateSystem(), beginIndex, endIndex() - beginIndex, null);
+    }
+
+    /**
+     * Normalizes only the dimensions returned by the given iterator, or all dimensions if the iterator is null.
+     * This is used for normalizing the result of a coordinate operation where a wrap around axis does not
+     * necessarily means that the ordinates need to be normalized along that axis.
+     *
+     * @param  cs          the coordinate system of this envelope CRS (as an argument because sometime already known).
+     * @param  beginIndex  index of the first ordinate value in {@link #ordinates} array. Non-zero for sub-envelopes.
+     * @param  count       number of coordinates, i.e. this envelope dimensions.
+     * @param  dimensions  the dimensions to check for normalization, or {@code null} for all dimensions.
+     * @return {@code true} if this envelope has been modified as a result of this method call.
+     */
+    final boolean normalize(final CoordinateSystem cs, final int beginIndex, final int count, final Iterator<Integer> dimensions) {
         boolean changed = false;
-        if (crs != null) {
-            final int d = ordinates.length >>> 1;
-            final int beginIndex = beginIndex();
-            final int dimension = endIndex() - beginIndex;
-            final CoordinateSystem cs = crs.getCoordinateSystem();
-            for (int i=0; i<dimension; i++) {
-                final int iLower = beginIndex + i;
-                final int iUpper = iLower + d;
-                final CoordinateSystemAxis axis = cs.getAxis(i);
-                final double  minimum = axis.getMinimumValue();
-                final double  maximum = axis.getMaximumValue();
-                final RangeMeaning rm = axis.getRangeMeaning();
-                if (RangeMeaning.EXACT.equals(rm)) {
-                    if (ordinates[iLower] < minimum) {ordinates[iLower] = minimum; changed = true;}
-                    if (ordinates[iUpper] > maximum) {ordinates[iUpper] = maximum; changed = true;}
-                } else if (RangeMeaning.WRAPAROUND.equals(rm)) {
-                    final double csSpan = maximum - minimum;
-                    if (csSpan > 0 && csSpan < Double.POSITIVE_INFINITY) {
-                        double o1 = ordinates[iLower];
-                        double o2 = ordinates[iUpper];
-                        if (Math.abs(o2-o1) >= csSpan) {
-                            /*
-                             * If the range exceed the CS span, then we have to replace it by the
-                             * full span, otherwise the range computed by the "else" block is too
-                             * small. The full range will typically be [-180 … 180]°.  However we
-                             * make a special case if the two bounds are multiple of the CS span,
-                             * typically [0 … 360]°. In this case the [0 … -0]° range matches the
-                             * original values and is understood by GeneralEnvelope as a range
-                             * spanning all the world.
-                             */
-                            if (o1 != minimum || o2 != maximum) {
-                                if ((o1 % csSpan) == 0 && (o2 % csSpan) == 0) {
-                                    ordinates[iLower] = +0.0;
-                                    ordinates[iUpper] = -0.0;
-                                } else {
-                                    ordinates[iLower] = minimum;
-                                    ordinates[iUpper] = maximum;
-                                }
-                                changed = true;
+        final int d = ordinates.length >>> 1;
+        for (int j=0; j<count; j++) {
+            final int i = (dimensions != null) ? dimensions.next() : j;
+            final int iLower = beginIndex + i;
+            final int iUpper = iLower + d;
+            final CoordinateSystemAxis axis = cs.getAxis(i);
+            final double  minimum = axis.getMinimumValue();
+            final double  maximum = axis.getMaximumValue();
+            final RangeMeaning rm = axis.getRangeMeaning();
+            if (RangeMeaning.EXACT.equals(rm)) {
+                if (ordinates[iLower] < minimum) {ordinates[iLower] = minimum; changed = true;}
+                if (ordinates[iUpper] > maximum) {ordinates[iUpper] = maximum; changed = true;}
+            } else if (RangeMeaning.WRAPAROUND.equals(rm)) {
+                final double csSpan = maximum - minimum;
+                if (csSpan > 0 && csSpan < Double.POSITIVE_INFINITY) {
+                    double o1 = ordinates[iLower];
+                    double o2 = ordinates[iUpper];
+                    if (Math.abs(o2-o1) >= csSpan) {
+                        /*
+                         * If the range exceed the CS span, then we have to replace it by the
+                         * full span, otherwise the range computed by the "else" block is too
+                         * small. The full range will typically be [-180 … 180]°.  However we
+                         * make a special case if the two bounds are multiple of the CS span,
+                         * typically [0 … 360]°. In this case the [0 … -0]° range matches the
+                         * original values and is understood by GeneralEnvelope as a range
+                         * spanning all the world.
+                         */
+                        if (o1 != minimum || o2 != maximum) {
+                            if ((o1 % csSpan) == 0 && (o2 % csSpan) == 0) {
+                                ordinates[iLower] = +0.0;
+                                ordinates[iUpper] = -0.0;
+                            } else {
+                                ordinates[iLower] = minimum;
+                                ordinates[iUpper] = maximum;
                             }
-                        } else {
-                            o1 = Math.floor((o1 - minimum) / csSpan) * csSpan;
-                            o2 = Math.floor((o2 - minimum) / csSpan) * csSpan;
-                            if (o1 != 0) {ordinates[iLower] -= o1; changed = true;}
-                            if (o2 != 0) {ordinates[iUpper] -= o2; changed = true;}
+                            changed = true;
                         }
+                    } else {
+                        o1 = Math.floor((o1 - minimum) / csSpan) * csSpan;
+                        o2 = Math.floor((o2 - minimum) / csSpan) * csSpan;
+                        if (o1 != 0) {ordinates[iLower] -= o1; changed = true;}
+                        if (o2 != 0) {ordinates[iUpper] -= o2; changed = true;}
                     }
                 }
             }
@@ -941,8 +958,6 @@ public class GeneralEnvelope extends Arr
         return changed;
     }
 
-    // Note: As of JDK 1.6.0_31, using {@linkplain #getLower(int)} in the first line crash the
-    // Javadoc tools, maybe because getLower/getUpper are defined in a non-public parent class.
     /**
      * Ensures that <var>lower</var> &lt;= <var>upper</var> for every dimensions.
      * If a {@linkplain #getUpper(int) upper ordinate value} is less than a
@@ -978,7 +993,7 @@ public class GeneralEnvelope extends Arr
             final double upper = ordinates[iUpper];
             if (isNegative(upper - lower)) {
                 final CoordinateSystemAxis axis = getAxis(crs, i);
-                if (axis != null && RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning())) {
+                if (isWrapAround(axis)) {
                     ordinates[iLower] = axis.getMinimumValue();
                     ordinates[iUpper] = axis.getMaximumValue();
                     changed = true;

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometry;
 
+import java.util.Set;
 import java.awt.geom.Point2D;
 import java.awt.geom.Line2D;
 import java.awt.geom.Ellipse2D;
@@ -32,6 +33,9 @@ import org.opengis.referencing.operation
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.referencing.j2d.ShapeUtilities;
+import org.apache.sis.internal.referencing.j2d.IntervalRectangle;
+import org.apache.sis.internal.referencing.CoordinateOperations;
+import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
 import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
@@ -351,9 +355,9 @@ public final class Shapes2D extends Stat
             D1=D2;
         }
         if (destination != null) {
-            destination.setRect(xmin, ymin, xmax-xmin, ymax-ymin);
+            destination.setRect(xmin, ymin, xmax - xmin, ymax - ymin);
         } else {
-            destination = new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax - ymin);
+            destination = new IntervalRectangle(xmin, ymin, xmax, ymax);
         }
         /*
          * Note: a previous version had an "assert" statement here comparing our calculation
@@ -539,8 +543,8 @@ public final class Shapes2D extends Stat
              * Forget any axes that are not of kind "WRAPAROUND". Then get the final
              * bit pattern indicating which points to test. Iterate over that bits.
              */
-            if ((toTest & 0x33333333) != 0 && !Envelopes.isWrapAround(targetCS.getAxis(0))) toTest &= 0xCCCCCCCC;
-            if ((toTest & 0xCCCCCCCC) != 0 && !Envelopes.isWrapAround(targetCS.getAxis(1))) toTest &= 0x33333333;
+            if ((toTest & 0x33333333) != 0 && !CoordinateOperations.isWrapAround(targetCS.getAxis(0))) toTest &= 0xCCCCCCCC;
+            if ((toTest & 0xCCCCCCCC) != 0 && !CoordinateOperations.isWrapAround(targetCS.getAxis(1))) toTest &= 0x33333333;
             while (toTest != 0) {
                 final int border = Integer.numberOfTrailingZeros(toTest);
                 final int bitMask = 1 << border;
@@ -569,6 +573,42 @@ public final class Shapes2D extends Stat
                 }
             }
         }
+        /*
+         * At this point we finished envelope transformation. Verify if some ordinates need to be "wrapped around"
+         * as a result of the coordinate operation.   This is usually the longitude axis where the source CRS uses
+         * the [-180 … +180]° range and the target CRS uses the [0 … 360]° range, or the converse. In such case we
+         * set the rectangle to the full range (we do not use the mechanism documented in Envelope2D) because most
+         * Rectangle2D implementations do not support spanning the anti-meridian. This results in larger rectangle
+         * than what would be possible with GeneralEnvelope or Envelope2D, but we try to limit the situation where
+         * this expansion is applied.
+         */
+        final Set<Integer> wrapAroundChanges;
+        if (operation instanceof AbstractCoordinateOperation) {
+            wrapAroundChanges = ((AbstractCoordinateOperation) operation).getWrapAroundChanges();
+        } else {
+            wrapAroundChanges = CoordinateOperations.wrapAroundChanges(sourceCRS, targetCS);
+        }
+        for (int dim : wrapAroundChanges) {                               // Empty in the vast majority of cases.
+            final CoordinateSystemAxis axis = targetCS.getAxis(dim);
+            final double minimum = axis.getMinimumValue();
+            final double maximum = axis.getMaximumValue();
+            final double o1, o2;
+            if (dim == 0) {
+                o1 = destination.getMinX();
+                o2 = destination.getMaxX();
+            } else {
+                o1 = destination.getMinY();
+                o2 = destination.getMaxY();
+            }
+            if (o1 < minimum || o2 > maximum) {
+                final double span = maximum - minimum;
+                if (dim == 0) {
+                    destination.setRect(minimum, destination.getY(), span, destination.getHeight());
+                } else {
+                    destination.setRect(destination.getX(), minimum, destination.getWidth(), span);
+                }
+            }
+        }
         if (warning != null) {
             Envelopes.recoverableException(Shapes2D.class, warning);
         }

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -27,7 +27,7 @@ import org.apache.sis.internal.metadata.
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.metadata.iso.citation.Citations;
 
-import static org.apache.sis.internal.util.Citations.getCodeSpace;
+import static org.apache.sis.metadata.iso.citation.Citations.getCodeSpace;
 
 
 /**
@@ -170,7 +170,7 @@ public final class Code {
             Identifier fallback = null;
             for (final Identifier identifier : identifiers) {
                 final String code = identifier.getCode();
-                if (code == null) continue; // Paranoiac check.
+                if (code == null) continue;                                                 // Paranoiac check.
                 if (code.regionMatches(true, 0, "urn:", 0, 4)) {
                     return new Code(identifier);
                 }

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/CoordinateOperations.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/CoordinateOperations.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/CoordinateOperations.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/CoordinateOperations.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -16,25 +16,57 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.Set;
+import java.util.Objects;
+import java.util.Collections;
+import javax.measure.UnitConverter;
+import javax.measure.IncommensurableException;
+import org.opengis.referencing.cs.RangeMeaning;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.GeneralDerivedCRS;
+import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.CoordinateOperationFactory;
 import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
+import org.apache.sis.internal.metadata.AxisDirections;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.system.SystemListener;
+import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.internal.jdk9.JDK9;
 
 
 /**
  * The default coordinate operation factory, provided in a separated class for deferring class loading
- * until first needed.
+ * until first needed. Contains also utility methods related to coordinate operations.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 0.8
  * @since   0.7
  * @module
  */
 public final class CoordinateOperations extends SystemListener {
     /**
-     * The factory.
+     * Cached values or {@link #wrapAroundChanges wrapAroundChanges(…)}, created when first needed.
+     * Indices are bit masks computed by {@link #changes changes(…)}. Since the most common "wrap around" axes
+     * are longitude at dimension 0 or 1, and some measurement of time (in climatology) at dimension 2 or 3,
+     * then the most likely values are (binary digits):
+     *
+     * {@preformat text
+     *     0000    0100    1000
+     *     0001    0101    1001
+     *     0010    0110    1010
+     * }
+     *
+     * The last decimal value is 10 (binary {@code 1010}); we don't need to cache more.
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    private static final Set<Integer>[] CACHE = new Set[11];
+
+    /**
+     * The system-wide default factory.
      */
     private static volatile DefaultCoordinateOperationFactory factory;
 
@@ -73,4 +105,157 @@ public final class CoordinateOperations
         }
         return c;
     }
+
+    /**
+     * Returns {@code true} if the given axis is of kind "Wrap Around".
+     * Defined here because used together with {@link #wrapAroundChanges wrapAroundChanges(…)}.
+     *
+     * @param  axis  the axis to test.
+     * @return {@code true} if the given axis has "wrap around" range meaning.
+     */
+    public static boolean isWrapAround(final CoordinateSystemAxis axis) {
+        return RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning());
+    }
+
+    /**
+     * Returns indices of target dimensions where "wrap around" may happen as a result of a coordinate operation.
+     * This is usually the longitude axis when the source CRS uses the [-180 … +180]° range and the target CRS
+     * uses the [0 … 360]° range, or the converse.
+     *
+     * @param  op  the coordinate operation for which to get "wrap around" target dimensions.
+     * @return target dimensions where "wrap around" may happen, or an empty set if none.
+     *
+     * @see AbstractCoordinateOperation#getWrapAroundChanges()
+     */
+    public static Set<Integer> wrapAroundChanges(final CoordinateOperation op) {
+        if (op instanceof AbstractCoordinateOperation) {
+            return ((AbstractCoordinateOperation) op).getWrapAroundChanges();
+        } else if (op != null) {
+            final CoordinateReferenceSystem source, target;
+            if ((source = op.getSourceCRS()) != null &&
+                (target = op.getTargetCRS()) != null)
+            {
+                return wrapAroundChanges(source, target.getCoordinateSystem());
+            }
+        }
+        return Collections.emptySet();
+    }
+
+    /**
+     * Computes indices of target dimensions where "wrap around" may happen as a result of a coordinate operation.
+     * This is usually the longitude axis when the source CRS uses the [-180 … +180]° range and the target CRS uses
+     * the [0 … 360]° range, or the converse.
+     *
+     * @param  source  the source of the coordinate operation.
+     * @param  target  the target of the coordinate operation.
+     * @return target dimensions where "wrap around" may happen, or an empty set if none.
+     */
+    public static Set<Integer> wrapAroundChanges(CoordinateReferenceSystem source, final CoordinateSystem target) {
+        long changes = changes(source.getCoordinateSystem(), target);
+        while (source instanceof GeneralDerivedCRS) {
+            source = ((GeneralDerivedCRS) source).getBaseCRS();
+            changes |= changes(source.getCoordinateSystem(), target);
+        }
+        final boolean useCache = (changes >= 0 && changes < CACHE.length);
+        if (useCache) {
+            /*
+             * In most cases we have an existing instance. Since WrapAroundAxes are immutable, if we got a reference,
+             * the object should be okay ("published" in memory model terminology) even if we did not synchronized.
+             * The reference however may not be visible in the array, but we will check later in a synchronized block.
+             */
+            final Set<Integer> existing = CACHE[(int) changes];
+            if (existing != null) {
+                return existing;
+            }
+        }
+        /*
+         * No existing instance found. Expand the 'changes' bit mask in an array of integers in order to create an
+         * unmodifiable List<Integer>. The list is for public API; internally, Apache SIS will use toBitMask(…).
+         */
+        long r = changes;
+        final Integer[] indices = new Integer[Long.bitCount(r)];
+        for (int i=0; i<indices.length; i++) {
+            final int dim = Long.numberOfTrailingZeros(r);
+            indices[i] = dim;
+            r &= ~(1L << dim);
+        }
+        final Set<Integer> dimensions = JDK9.setOf(indices);
+        if (useCache) {
+            synchronized (CACHE) {
+                final Set<Integer> existing = CACHE[(int) changes];
+                if (existing != null) {
+                    return existing;
+                }
+                CACHE[(int) changes] = dimensions;
+            }
+        }
+        return dimensions;
+    }
+
+    /**
+     * Returns the packed indices of target dimensions where ordinate values may need to be wrapped around.
+     * This method matches target coordinate system axes having {@link RangeMeaning#WRAPAROUND} with source
+     * axes, then verifies if the range of values changed (taking unit conversions in account). A target
+     * dimension {@code i} may need to "wrap around" the coordinate values if the {@code 1 << i} bit is set.
+     * If there is no change, then the value is zero.
+     */
+    private static long changes(final CoordinateSystem source, final CoordinateSystem target) {
+        long changes = 0;
+        if (source != target) {                                 // Optimization for a common case.
+            /*
+             * Get the dimensions of all axes in the source coordinate system as bit fields.
+             * We create this list first because we may iterate more than once on those axes
+             * and we want to clear the axes that we already matched. We use a bitmask for
+             * efficiency, with the bits of dimensions to consider set to 1.
+             *
+             * Note: a previous version was creating a list of "wraparound" axes only. We removed that filter
+             * because a target wraparound axis may match a source infinite axis. For example when converting
+             * dates on a temporal axis (with infinite span toward past and future) to months on a climatology
+             * axis (January to December months without year), the same cycle is repeated after every 12 months
+             * even if the source axis had no cycle.
+             */
+            long isWrapAroundAxis = (1 << source.getDimension()) - 1;
+            /*
+             * For each "wrap around" axis in the target CRS, search a matching axis in the source CRS
+             * which is also "wrap around", is colinear and uses compatible unit of measurement. There
+             * is usually at most one "wrap around" axis, but this code is nevertheless generic enough
+             * for an arbitrary amount of axes.
+             */
+            final int dim = Math.min(Long.SIZE, target.getDimension());
+compare:    for (int i=0; i<dim; i++) {
+                final CoordinateSystemAxis axis = target.getAxis(i);
+                if (isWrapAround(axis)) {
+                    long candidates = isWrapAroundAxis;
+                    do {
+                        final long mask = Long.lowestOneBit(candidates);
+                        final CoordinateSystemAxis src = source.getAxis(Long.numberOfTrailingZeros(mask));
+                        if (Objects.equals(AxisDirections.absolute(src .getDirection()),
+                                           AxisDirections.absolute(axis.getDirection())))
+                        {
+                            try {
+                                final UnitConverter c  = src.getUnit().getConverterToAny(axis.getUnit());
+                                final double minimum   = axis.getMinimumValue();
+                                final double maximum   = axis.getMaximumValue();
+                                final double tolerance = (maximum - minimum) * Numerics.COMPARISON_THRESHOLD;
+                                if (!Numerics.epsilonEqual(c.convert(src.getMinimumValue()), minimum, tolerance) ||
+                                    !Numerics.epsilonEqual(c.convert(src.getMaximumValue()), maximum, tolerance))
+                                {
+                                    changes |= (1 << i);
+                                }
+                                isWrapAroundAxis &= ~mask;      // We are done with this source axis.
+                                if (isWrapAroundAxis == 0) {
+                                    break compare;              // Useless to continue if there is no more source axis.
+                                }
+                                continue compare;               // Match next pair of wrap around axes.
+                            } catch (IncommensurableException e) {
+                                // Ignore (should not happen often). We will try to match another pair of axes.
+                            }
+                        }
+                        candidates &= ~mask;        // Unable to use that axis. Check if we can use another one.
+                    } while (candidates != 0);
+                }
+            }
+        }
+        return changes;
+    }
 }

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -40,10 +40,10 @@ import org.opengis.referencing.datum.Tem
 import org.opengis.referencing.operation.CoordinateOperationFactory;
 import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.Conversion;
-import org.apache.sis.internal.metadata.ReferencingServices;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.internal.metadata.EllipsoidalHeightCombiner;
 import org.apache.sis.internal.referencing.provider.TransverseMercator;
 import org.apache.sis.internal.referencing.provider.PolarStereographicA;
 import org.apache.sis.measure.Latitude;
@@ -252,7 +252,7 @@ public class GeodeticObjectBuilder exten
      * @return {@code this}, for method calls chaining.
      * @throws FactoryException if the operation method for the Transverse Mercator projection can not be obtained.
      *
-     * @see CommonCRS#UTM(double, double)
+     * @see CommonCRS#universal(double, double)
      */
     public GeodeticObjectBuilder setTransverseMercator(TransverseMercator.Zoner zoner, double latitude, double longitude)
             throws FactoryException
@@ -424,7 +424,13 @@ public class GeodeticObjectBuilder exten
      * @throws FactoryException if the object creation failed.
      */
     public CoordinateReferenceSystem createCompoundCRS(final CoordinateReferenceSystem... components) throws FactoryException {
-        return ReferencingServices.getInstance().createCompoundCRS(getCRSFactory(), getCSFactory(), properties, components);
+        return new EllipsoidalHeightCombiner() {
+            @Override public void initialize(final int factoryTypes) {
+                if ((factoryTypes & CRS)       != 0) crsFactory = getCRSFactory();
+                if ((factoryTypes & CS)        != 0)  csFactory = getCSFactory();
+                if ((factoryTypes & OPERATION) != 0)  opFactory = getCoordinateOperationFactory();
+            }
+        }.createCompoundCRS(properties, components);
     }
 
     /**

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -33,6 +33,8 @@ import org.opengis.referencing.crs.*;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.PrimeMeridian;
+import org.opengis.referencing.datum.VerticalDatum;
+import org.opengis.referencing.datum.VerticalDatumType;
 import org.opengis.referencing.operation.CoordinateOperationFactory;
 import org.opengis.util.FactoryException;
 import org.apache.sis.internal.system.DefaultFactories;
@@ -173,6 +175,30 @@ public final class ReferencingUtilities
     }
 
     /**
+     * Returns {@code true} if the type of the given datum is ellipsoidal. A vertical datum is not allowed
+     * to be ellipsoidal according ISO 19111, but Apache SIS relaxes this restriction in some limited cases,
+     * for example when parsing a string in the legacy WKT 1 format. Apache SIS should not expose those
+     * vertical heights as much as possible, and instead try to combine them with three-dimensional
+     * geographic or projected CRS as soon as it can.
+     *
+     * @param  datum  the datum to test, or {@code null} if none.
+     * @return {@code true} if the given datum is non null and of ellipsoidal type.
+     *
+     * @see org.apache.sis.internal.metadata.VerticalDatumTypes#ELLIPSOIDAL
+     *
+     * @since 0.8
+     */
+    public static boolean isEllipsoidalHeight(final VerticalDatum datum) {
+        if (datum != null) {
+            final VerticalDatumType type = datum.getVerticalDatumType();
+            if (type != null) {
+                return "ELLIPSOIDAL".equalsIgnoreCase(type.name());
+            }
+        }
+        return false;
+    }
+
+    /**
      * Returns the ellipsoid used by the specified coordinate reference system, provided that the two first dimensions
      * use an instance of {@link GeographicCRS}. Otherwise (i.e. if the two first dimensions are not geographic),
      * returns {@code null}.
@@ -271,15 +297,14 @@ public final class ReferencingUtilities
      * </ul></div>
      *
      * @param  object    the identified object to view as a properties map.
-     * @param  excludes  the keys of properties to exclude from the map.
      * @return a view of the identified object properties.
      *
      * @see IdentifiedObjects#getProperties(IdentifiedObject, String...)
      *
      * @since 0.7
      */
-    public static Map<String,?> getPropertiesForModifiedCRS(final IdentifiedObject object, final String... excludes) {
-        final Map<String,?> properties = IdentifiedObjects.getProperties(object, excludes);
+    public static Map<String,?> getPropertiesForModifiedCRS(final IdentifiedObject object) {
+        final Map<String,?> properties = IdentifiedObjects.getProperties(object, IdentifiedObject.IDENTIFIERS_KEY);
         final Identifier id = (Identifier) properties.get(IdentifiedObject.NAME_KEY);
         if (id != null) {
             String name = id.getCode();
@@ -327,13 +352,16 @@ public final class ReferencingUtilities
      */
     public static StringBuilder toPropertyName(final Class<?> base, final Class<?> type) {
         final UML uml = type.getAnnotation(UML.class);
-        if (uml != null && uml.specification() == Specification.ISO_19111) {
-            final String name = uml.identifier();
-            final int length = name.length();
-            final StringBuilder buffer = new StringBuilder(length).append(name, name.indexOf('_') + 1, length);
-            if (buffer.length() != 0) {
-                buffer.setCharAt(0, Character.toLowerCase(buffer.charAt(0)));
-                return buffer;
+        if (uml != null) {
+            final Specification spec = uml.specification();
+            if (spec == Specification.ISO_19111 || spec == Specification.ISO_19111_2) {
+                final String name = uml.identifier();
+                final int length = name.length();
+                final StringBuilder buffer = new StringBuilder(length).append(name, name.indexOf('_') + 1, length);
+                if (buffer.length() != 0) {
+                    buffer.setCharAt(0, Character.toLowerCase(buffer.charAt(0)));
+                    return buffer;
+                }
             }
         }
         for (final Class<?> c : type.getInterfaces()) {

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -68,6 +68,11 @@ public final class Resources extends Ind
         public static final short AmbiguousEllipsoid_1 = 1;
 
         /**
+         * Can not create objects of type ‘{0}’ from combined URI.
+         */
+        public static final short CanNotCombineUriAsType_1 = 79;
+
+        /**
          * Can not compute the coordinate operation derivative.
          */
         public static final short CanNotComputeDerivative = 2;
@@ -78,7 +83,7 @@ public final class Resources extends Ind
         public static final short CanNotConcatenateTransforms_2 = 3;
 
         /**
-         * Can not create an object of group “{1}” as an instance of class ‘{0}’.
+         * Can not create an object of type “{1}” as an instance of class ‘{0}’.
          */
         public static final short CanNotCreateObjectAsInstanceOf_2 = 4;
 
@@ -98,6 +103,11 @@ public final class Resources extends Ind
         public static final short CanNotMapAxisToDirection_1 = 6;
 
         /**
+         * Can not parse component {1} in the combined {0,choice,0#URN|1#URL}.
+         */
+        public static final short CanNotParseCombinedReference_2 = 78;
+
+        /**
          * Target dimension {0} depends on excluded source dimensions.
          */
         public static final short CanNotSeparateTargetDimension_1 = 7;
@@ -149,6 +159,18 @@ public final class Resources extends Ind
         public static final short DuplicatedParameterName_4 = 16;
 
         /**
+         * Compound coordinate reference systems can not contain two {0,choice,1#horizontal|2#vertical}
+         * components.
+         */
+        public static final short DuplicatedSpatialComponents_1 = 76;
+
+        /**
+         * Compound coordinate reference systems should not contain ellipsoidal height. Use a
+         * three-dimensional {0,choice,0#geographic|1#projected} system instead.
+         */
+        public static final short EllipsoidalHeightNotAllowed_1 = 77;
+
+        /**
          * There is no factory for version {1} of “{0}” authority. Fallback on default version for
          * objects creation.
          */
@@ -415,6 +437,11 @@ public final class Resources extends Ind
         public static final short SingularMatrix = 63;
 
         /**
+         * Combined URI contains unexpected components.
+         */
+        public static final short UnexpectedComponentInURI = 80;
+
+        /**
          * Unexpected dimension for a coordinate system of type ‘{0}’.
          */
         public static final short UnexpectedDimensionForCS_1 = 64;

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties [ISO-8859-1] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties [ISO-8859-1] Sat Dec  9 10:57:44 2017
@@ -42,12 +42,14 @@ NonConformCRS_3                   = The
 #
 # Error messages (to be used in exceptions)
 #
-CanNotConcatenateTransforms_2     = Can not concatenate transforms \u201c{0}\u201d and \u201c{1}\u201d.
+CanNotCombineUriAsType_1          = Can not create objects of type \u2018{0}\u2019 from combined URI.
 CanNotComputeDerivative           = Can not compute the coordinate operation derivative.
-CanNotCreateObjectAsInstanceOf_2  = Can not create an object of group \u201c{1}\u201d as an instance of class \u2018{0}\u2019.
+CanNotConcatenateTransforms_2     = Can not concatenate transforms \u201c{0}\u201d and \u201c{1}\u201d.
+CanNotCreateObjectAsInstanceOf_2  = Can not create an object of type \u201c{1}\u201d as an instance of class \u2018{0}\u2019.
 CanNotInferGridSizeFromValues_1   = Can not infer a grid size from the given values in {0} range.
 CanNotInstantiateGeodeticObject_1 = Can not instantiate geodetic object for \u201c{0}\u201d.
 CanNotMapAxisToDirection_1        = Can not map an axis from the specified coordinate system to the \u201c{0}\u201d direction.
+CanNotParseCombinedReference_2    = Can not parse component {1} in the combined {0,choice,0#URN|1#URL}.
 CanNotSeparateTargetDimension_1   = Target dimension {0} depends on excluded source dimensions.
 CanNotTransformEnvelopeToGeodetic = Can not transform envelope to a geodetic reference system.
 CanNotUseGeodeticParameters_2     = Can not use the {0} geodetic parameters: {1}
@@ -55,6 +57,8 @@ ColinearAxisDirections_2          = Axis
 CoordinateOperationNotFound_2     = Coordinate conversion of transformation from system \u201c{0}\u201d to \u201c{1}\u201d has not been found.
 DatumOriginShallBeDate            = Origin of temporal datum shall be a date.
 DuplicatedParameterName_4         = Name or alias for parameter \u201c{0}\u201d at index {1} conflict with name \u201c{2}\u201d at index {3}.
+DuplicatedSpatialComponents_1     = Compound coordinate reference systems can not contain two {0,choice,1#horizontal|2#vertical} components.
+EllipsoidalHeightNotAllowed_1     = Compound coordinate reference systems should not contain ellipsoidal height. Use a three-dimensional {0,choice,0#geographic|1#projected} system instead.
 IllegalAxisDirection_2            = Coordinate system of class \u2018{0}\u2019 can not have axis in the {1} direction.
 IllegalOperationDimension_3       = Dimensions of \u201c{0}\u201d operation can not be ({1} \u2192 {2}).
 IllegalOperationForValueClass_1   = This operation can not be applied to values of class \u2018{0}\u2019.
@@ -94,6 +98,7 @@ NoSuchOperationMethod_1           = No o
 ParameterNotFound_2               = No parameter named \u201c{1}\u201d has been found in \u201c{0}\u201d.
 RecursiveCreateCallForCode_2      = Recursive call while creating an object of type \u2018{0}\u2019 for code \u201c{1}\u201d.
 SingularMatrix                    = Matrix is singular.
+UnexpectedComponentInURI          = Combined URI contains unexpected components.
 UnexpectedDimensionForCS_1        = Unexpected dimension for a coordinate system of type \u2018{0}\u2019.
 UnitlessParameter_1               = Parameter \u201c{0}\u201d does not expect unit.
 UnknownAuthority_1                = Authority \u201c{0}\u201d is unknown.

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties [ISO-8859-1] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties [ISO-8859-1] Sat Dec  9 10:57:44 2017
@@ -47,19 +47,23 @@ NonConformCRS_3                   = La d
 #
 # Error messages (to be used in exceptions)
 #
-CanNotConcatenateTransforms_2     = Les transformations \u00ab\u202f{0}\u202f\u00bb et \u00ab\u202f{1}\u202f\u00bb ne peuvent pas \u00eatre combin\u00e9es.
+CanNotCombineUriAsType_1          = Ne peut pas cr\u00e9er d\u2019objets de type \u2018{0}\u2019 \u00e0 partir d\u2019un URI combin\u00e9.
 CanNotComputeDerivative           = La d\u00e9riv\u00e9 de l\u2019op\u00e9ration sur les coordonn\u00e9es ne peut pas \u00eatre calcul\u00e9e.
-CanNotCreateObjectAsInstanceOf_2  = Ne peut pas cr\u00e9er un objet du groupe \u00ab\u202f{1}\u202f\u00bb comme une instance de la classe \u2018{0}\u2019.
+CanNotConcatenateTransforms_2     = Les transformations \u00ab\u202f{0}\u202f\u00bb et \u00ab\u202f{1}\u202f\u00bb ne peuvent pas \u00eatre combin\u00e9es.
+CanNotCreateObjectAsInstanceOf_2  = Ne peut pas cr\u00e9er un objet du type \u00ab\u202f{1}\u202f\u00bb comme une instance de la classe \u2018{0}\u2019.
 CanNotInferGridSizeFromValues_1   = Ne peut pas inf\u00e9rer une taille de grille \u00e0 partir des valeurs donn\u00e9es dans la plage {0}.
 CanNotInstantiateGeodeticObject_1 = Ne peut pas cr\u00e9er l\u2019objet g\u00e9od\u00e9tique pour \u00ab\u202f{0}\u202f\u00bb.
 CanNotMapAxisToDirection_1        = Aucun axe du syst\u00e8me de coordonn\u00e9es sp\u00e9cifi\u00e9 n\u2019a pu \u00eatre associ\u00e9 \u00e0 la direction \u00ab\u202f{0}\u202f\u00bb.
 CanNotSeparateTargetDimension_1   = La dimension de destination {0} d\u00e9pend de dimensions sources qui ont \u00e9t\u00e9 exclues.
+CanNotParseCombinedReference_2    = Ne peut pas d\u00e9coder la composante {1} dans l\u2019{0,choice,0#URN|1#URL} combin\u00e9.
 CanNotTransformEnvelopeToGeodetic = Ne peut pas transformer l\u2019enveloppe vers un r\u00e9f\u00e9rentiel g\u00e9od\u00e9sique.
 CanNotUseGeodeticParameters_2     = Ne peut pas utiliser les param\u00e8tres g\u00e9od\u00e9siques {0}\u202f: {1}
 ColinearAxisDirections_2          = Les directions d\u2019axes {0} et {1} sont colin\u00e9aires.
 CoordinateOperationNotFound_2     = La conversion ou transformation des coordonn\u00e9es du syst\u00e8me \u00ab\u202f{0}\u202f\u00bb vers \u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e.
 DatumOriginShallBeDate            = L\u2019origine d\u2019un r\u00e9f\u00e9rentiel temporel doit \u00eatre une date.
 DuplicatedParameterName_4         = Le nom ou un alias pour le param\u00e8tre \u00ab\u202f{0}\u202f\u00bb \u00e0 l\u2019index {1} duplique le nom \u00ab\u202f{2}\u202f\u00bb \u00e0 l\u2019index {3}.
+DuplicatedSpatialComponents_1     = Un syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es ne peut pas contenir deux composantes {0,choice,1#horizontales|2#verticales}.
+EllipsoidalHeightNotAllowed_1     = Un syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es ne devrait pas contenir une hauteur ellipso\u00efdale. Utilisez plut\u00f4t un syst\u00e8me {0,choice,0#g\u00e9ographique|1#projet\u00e9} \u00e0 trois dimensions.
 IllegalAxisDirection_2            = Les syst\u00e8mes de coordonn\u00e9es de classe \u2018{0}\u2019 ne peuvent pas avoir d\u2019axe dans la direction \u00ab\u202f{1}\u202f\u00bb.
 IllegalOperationDimension_3       = Les dimensions de l\u2019op\u00e9ration \u00ab\u202f{0}\u202f\u00bb ne peuvent pas \u00eatre ({1} \u2192 {2}).
 IllegalOperationForValueClass_1   = Cette op\u00e9ration ne peut pas s\u2019appliquer aux valeurs de classe \u2018{0}\u2019.
@@ -99,6 +103,7 @@ NoSuchOperationMethod_1           = Aucu
 ParameterNotFound_2               = Aucun param\u00e8tre nomm\u00e9 \u00ab\u202f{1}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9 dans \u00ab\u202f{0}\u202f\u00bb.
 RecursiveCreateCallForCode_2      = Appels r\u00e9cursifs lors de la cr\u00e9ation d\u2019un objet de type \u2018{0}\u2019 pour le code \u00ab\u202f{1}\u202f\u00bb.
 SingularMatrix                    = La matrice est singuli\u00e8re.
+UnexpectedComponentInURI          = L\u2019URI combin\u00e9 contient des composantes qui n\u2019\u00e9taient pas attendues.
 UnexpectedDimensionForCS_1        = Dimension inattendue pour un syst\u00e8me de coordonn\u00e9es de type \u2018{0}\u2019.
 UnitlessParameter_1               = Le param\u00e8tre \u00ab\u202f{0}\u202f\u00bb n\u2019attend pas d\u2019unit\u00e9.
 UnknownAuthority_1                = L\u2019autorit\u00e9 \u00ab\u202f{0}\u202f\u00bb n\u2019est pas reconnue.

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -91,6 +91,7 @@ import org.apache.sis.internal.metadata.
 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.util.CharSequences;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
@@ -660,6 +661,8 @@ public final class ServicesForMetadata e
      * If the given properties are empty and the {@code mtFactory} is the system default, then this method
      * returns the system default {@code CoordinateOperationFactory} instead of creating a new one.
      *
+     * <p>It is okay to set all parameters to {@code null} in order to get the system default factory.</p>
+     *
      * @param  properties  the default properties.
      * @param  mtFactory   the math transform factory to use.
      * @param  crsFactory  the factory to use if the operation factory needs to create CRS for intermediate steps.
@@ -692,13 +695,15 @@ public final class ServicesForMetadata e
      * Returns the properties of the given object.
      *
      * @param  object  the object from which to get the properties.
+     * @param  keepId  {@code true} for preserving the identifiers, {@code false} for discarding them.
      * @return the properties of the given object.
      *
      * @since 0.6
      */
     @Override
-    public Map<String,?> getProperties(final IdentifiedObject object) {
-        return IdentifiedObjects.getProperties(object);
+    public Map<String,?> getProperties(final IdentifiedObject object, final boolean keepId) {
+        return IdentifiedObjects.getProperties(object, keepId ? CharSequences.EMPTY_ARRAY
+                : new String[] {IdentifiedObject.IDENTIFIERS_KEY});
     }
 
     /**

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -19,9 +19,11 @@ package org.apache.sis.internal.referenc
 import javax.measure.Unit;
 import javax.measure.Quantity;
 import javax.measure.quantity.Angle;
+import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.GeneralParameterValue;
+import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
@@ -46,6 +48,7 @@ import org.apache.sis.measure.Units;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.internal.util.Constants;
 
 
 /**
@@ -246,6 +249,32 @@ public final class WKTUtilities extends
     }
 
     /**
+     * Returns {@code true} if the given parameter is defined in the EPSG code space. We handle EPSG
+     * parameters in a special way because Apache SIS uses the EPSG geodetic dataset as the primary
+     * source of coordinate operation definitions.
+     *
+     * <p>We intentionally don't define {@code isEPSG(OperationMethod)} method because the operation
+     * method may be the inverse of an EPSG method (for example "Inverse of Mercator (variant A)")
+     * which would not be recognized. Instead, {@code isEPSG(method.getParameters())} should work.</p>
+     *
+     * @param  descriptor   the parameter or group of parameters to inspect.
+     * @param  ifUndefined  the value to return if the code space is undefined.
+     * @return whether the given parameter is an EPSG parameter.
+     */
+    public static boolean isEPSG(final GeneralParameterDescriptor descriptor, final boolean ifUndefined) {
+        if (descriptor != null) {
+            final Identifier id = descriptor.getName();
+            if (id != null) {
+                final String cs = id.getCodeSpace();
+                if (cs != null) {
+                    return Constants.EPSG.equalsIgnoreCase(cs);
+                }
+            }
+        }
+        return ifUndefined;
+    }
+
+    /**
      * Returns the WKT type of the given interface.
      *
      * For {@link CoordinateSystem} base type, the returned value shall be one of

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -31,7 +31,7 @@ import org.apache.sis.util.ArgumentCheck
  * used in double-double arithmetic.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 0.8
  * @since   0.5
  * @module
  */
@@ -181,6 +181,26 @@ final class AffineMatrix implements Exte
     }
 
     /**
+     * Compares this matrix with the given object for equality, including error terms (if any).
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof AffineMatrix) {
+            final AffineMatrix other = (AffineMatrix) obj;
+            return transform.equals(other.transform) && Arrays.equals(errors, other.errors);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code value for this matrix.
+     */
+    @Override
+    public int hashCode() {
+        return (transform.hashCode() * 31 + Arrays.hashCode(errors)) ^ (int) serialVersionUID;
+    }
+
+    /**
      * Returns a string representation of this matrix.
      *
      * @return a string representation of this matrix.

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Affine.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Affine.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Affine.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Affine.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -57,7 +57,7 @@ import org.apache.sis.referencing.operat
  * </table>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.6
+ * @version 0.8
  * @since   0.5
  * @module
  */
@@ -283,6 +283,23 @@ public final class Affine extends Abstra
     }
 
     /**
+     * Returns parameter values for an identity transform of the given input and output dimensions.
+     * Callers can modify the returned parameters if desired.
+     *
+     * @param  dimension  the number of source and target dimensions.
+     * @return parameters for an identity transform of the given dimensions.
+     *
+     * @since 0.8
+     */
+    public static ParameterValueGroup identity(int dimension) {
+        final ParameterValueGroup values = TensorParameters.WKT1.createValueGroup(
+                Collections.singletonMap(NAME_KEY, Constants.AFFINE));
+        values.parameter(Constants.NUM_COL).setValue(++dimension);
+        values.parameter(Constants.NUM_ROW).setValue(  dimension);
+        return values;
+    }
+
+    /**
      * Returns the parameter values for the given matrix. This method is invoked by implementations of
      * {@link org.apache.sis.referencing.operation.transform.AbstractMathTransform#getParameterValues()}.
      *

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AlbersEqualArea.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AlbersEqualArea.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AlbersEqualArea.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AlbersEqualArea.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -66,7 +66,7 @@ public final class AlbersEqualArea exten
 
     /**
      * The operation parameter descriptor for the <cite>Latitude of 2nd standard parallel</cite> parameter value.
-     * Valid values range is [-90 … 90]° and default value is the value given to the {@link #STANDARD_PARALLEL_2}
+     * Valid values range is [-90 … 90]° and default value is the value given to the {@link #STANDARD_PARALLEL_1}
      * parameter.
      */
     public static final ParameterDescriptor<Double> STANDARD_PARALLEL_2;

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -226,7 +226,8 @@ public abstract class GeocentricAffine e
 
     /**
      * Returns the parameters for creating a datum shift operation.
-     * The operation method will be one of the {@code GeocentricAffine} subclasses.
+     * The operation method will be one of the {@code GeocentricAffine} subclasses,
+     * unless the specified {@code method} argument is {@link DatumShiftMethod#NONE}.
      * 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.
@@ -235,19 +236,18 @@ public abstract class GeocentricAffine e
      * 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).
+     * @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, or {@code null} if there is no datum shift information.
+     * @param  method      the preferred datum shift method. Note that {@code createParameters(…)} may overwrite.
      * @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 CoordinateSystem targetCS, final Matrix datumShift, DatumShiftMethod method)
     {
         final boolean isEllipsoidal = (sourceCS instanceof EllipsoidalCS);
-        if (!(isEllipsoidal ? targetCS instanceof EllipsoidalCS
-                            : targetCS instanceof CartesianCS && sourceCS instanceof CartesianCS))
+        if (!(isEllipsoidal ? (targetCS instanceof EllipsoidalCS)
+                            : (targetCS instanceof CartesianCS && sourceCS instanceof CartesianCS)))
         {
             return null;        // Coordinate systems are not two EllipsoidalCS or two CartesianCS.
         }
@@ -256,43 +256,93 @@ public abstract class GeocentricAffine e
         if (dimension != targetCS.getDimension()) {
             dimension  = 4;     // Any value greater than 3 means "mismatched dimensions" for this method.
         }
+        if (method == DatumShiftMethod.NONE) {
+            if (dimension <= 3) {
+                return Affine.identity(dimension);
+            } else if (isEllipsoidal) {
+                final ParameterDescriptorGroup descriptor;
+                switch (sourceCS.getDimension()) {
+                    case 2: descriptor = Geographic2Dto3D.PARAMETERS; break;
+                    case 3: descriptor = Geographic3Dto2D.PARAMETERS; break;
+                    default: return null;
+                }
+                return descriptor.createValue();
+            } else {
+                return null;
+            }
+        }
         /*
          * 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
+         * The matrix may not be convertible, in which case we will let the caller
          * uses the matrix directly in Cartesian geocentric coordinates.
          */
         final BursaWolfParameters parameters = new BursaWolfParameters(null, null);
-        try {
+        if (datumShift != null) try {
             parameters.setPositionVectorTransformation(datumShift, BURSAWOLF_TOLERANCE);
         } catch (IllegalArgumentException e) {
             log(Loggers.COORDINATE_OPERATION, "createParameters", e);
             return null;
+        } else {
+            /*
+             * If there is no datum shift parameters (not to be confused with identity), then those parameters
+             * are assumed unknown. Using the most accurate methods would give a false impression of accuracy,
+             * so we use the fastest method instead. Since all parameter values are zero, Apache SIS should use
+             * the AbridgedMolodenskyTransform2D optimization.
+             */
+            method = DatumShiftMethod.ABRIDGED_MOLODENSKY;
         }
         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.
+         * Following "if" blocks are ordered from most accurate to less accurate datum shift method
+         * supported by GeocentricAffine subclasses (except NONE which has already been handled).
+         * Special cases:
+         *
+         *   - If the datum shift is applied between geocentric CRS, then the Molodensky approximations do not apply
+         *     as they are designed for transformations between geographic CRS only. User preference is then ignored.
+         *
+         *   - Molodensky methods are approximations for datum shifts having only translation terms in their Bursa-Wolf
+         *     parameters. If there is also a scale or rotation terms, then we can not use Molodensky methods. The user
+         *     preference is then ignored.
          */
         if (!isEllipsoidal) {
-            useMolodensky = false;
+            method = DatumShiftMethod.GEOCENTRIC_DOMAIN;
             descriptor = isTranslation ? GeocentricTranslation.PARAMETERS
                                        : PositionVector7Param .PARAMETERS;
-        } else {
-            if (!isTranslation) {
-                useMolodensky = false;
-                descriptor = (dimension >= 3) ? PositionVector7Param3D.PARAMETERS
-                                              : PositionVector7Param2D.PARAMETERS;
-            } else if (!useMolodensky) {
+        } else if (!isTranslation) {
+            method = DatumShiftMethod.GEOCENTRIC_DOMAIN;
+            descriptor = (dimension >= 3) ? PositionVector7Param3D.PARAMETERS
+                                          : PositionVector7Param2D.PARAMETERS;
+        } else switch (method) {
+            case GEOCENTRIC_DOMAIN: {
                 descriptor = (dimension >= 3) ? GeocentricTranslation3D.PARAMETERS
                                               : GeocentricTranslation2D.PARAMETERS;
-            } else {
+                break;
+            }
+            case MOLODENSKY: {
                 descriptor = Molodensky.PARAMETERS;
+                break;
+            }
+            case ABRIDGED_MOLODENSKY: {
+                descriptor = AbridgedMolodensky.PARAMETERS;
+                break;
             }
+            default: throw new AssertionError(method);
         }
+        /*
+         * Following lines will set all Bursa-Wolf parameter values (scale, translation
+         * and rotation terms). In the particular case of Molodensky method, we have an
+         * additional parameter for the number of source and target dimensions (2 or 3).
+         */
         final Parameters values = createParameters(descriptor, parameters, isTranslation);
-        if (useMolodensky && dimension <= 3) {
-            values.getOrCreate(Molodensky.DIMENSION).setValue(dimension);
+        switch (method) {
+            case MOLODENSKY:
+            case ABRIDGED_MOLODENSKY: {
+                if (dimension <= 3) {
+                    values.getOrCreate(Molodensky.DIMENSION).setValue(dimension);
+                }
+                break;
+            }
         }
         return values;
     }

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -31,6 +31,7 @@ import org.opengis.util.GenericName;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.ParameterNotFoundException;
+import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.Projection;
@@ -127,6 +128,15 @@ public abstract class MapProjection exte
     }
 
     /**
+     * The three-dimensional counterpart of this two-dimensional map projection.
+     * This is created when first needed.
+     *
+     * @see #redimension(int, int)
+     * @see GeodeticOperation#redimensioned
+     */
+    private OperationMethod redimensioned;
+
+    /**
      * Constructs a math transform provider from a set of parameters. The provider
      * {@linkplain #getIdentifiers() identifiers} will be the same than the parameter ones.
      *
@@ -147,6 +157,26 @@ public abstract class MapProjection exte
     }
 
     /**
+     * Returns this operation method with the specified number of dimensions.
+     * The number of dimensions can be only 2 or 3, and must be the same for source and target CRS.
+     *
+     * @return the redimensioned projection method, or {@code this} if no change is needed.
+     *
+     * @since 0.8
+     */
+    @Override
+    public final OperationMethod redimension(final int sourceDimensions, final int targetDimensions) {
+        if (sourceDimensions != 3 || targetDimensions != 3) {
+            return super.redimension(sourceDimensions, targetDimensions);
+        } else synchronized (this) {
+            if (redimensioned == null) {
+                redimensioned = new MapProjection3D(this);
+            }
+            return redimensioned;
+        }
+    }
+
+    /**
      * Validates the given parameter value. This method duplicates the verification already
      * done by {@link org.apache.sis.parameter.DefaultParameterValue#setValue(Object, Unit)}.
      * But we check again because we have no guarantee that the parameters given by the user

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -19,6 +19,7 @@ package org.apache.sis.internal.referenc
 import java.util.Map;
 import java.util.Collections;
 import javax.xml.bind.annotation.XmlTransient;
+import javax.measure.Unit;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
@@ -34,9 +35,11 @@ import org.apache.sis.referencing.datum.
 import org.apache.sis.referencing.operation.transform.MolodenskyTransform;
 import org.apache.sis.internal.referencing.NilReferencingObject;
 import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.measure.Units;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.Debug;
 
 
@@ -226,6 +229,7 @@ public final class Molodensky extends Ge
         final Ellipsoid target = new Ellipsoid(name, ta, tb, -Δa, -Δf);
         source.other = target;
         target.other = source;
+        source.computeDifferences(values);
         return MolodenskyTransform.createGeodeticTransformation(factory,
                 source, sourceDimensions >= 3,
                 target, targetDimensions >= 3,
@@ -258,7 +262,7 @@ public final class Molodensky extends Ge
     @SuppressWarnings("serial")
     private static final class Ellipsoid extends DefaultEllipsoid {
         /** The EPSG parameter values, or NaN if unspecified. */
-        private final double Δa, Δf;
+        private double Δa, Δf;
 
         /** The ellipsoid for which Δa and Δf are valid. */
         Ellipsoid other;
@@ -270,6 +274,45 @@ public final class Molodensky extends Ge
             this.Δf = Δf;
         }
 
+        /**
+         * Computes Δa and Δf now if not already done and tries to store the result in the given parameters.
+         * The parameters are set in order to complete them when the user specified the OGC parameters (axis
+         * lengths) and not the EPSG ones (axis and flattening differences).
+         */
+        void computeDifferences(final Parameters values) {
+            if (Double.isNaN(Δa)) {
+                Δa = super.semiMajorAxisDifference(other);
+                setIfPresent(values, AXIS_LENGTH_DIFFERENCE, Δa, getAxisUnit());
+            }
+            if (Double.isNaN(Δf)) {
+                Δf = super.flatteningDifference(other);
+                setIfPresent(values, FLATTENING_DIFFERENCE, Δf, Units.UNITY);
+            }
+        }
+
+        /**
+         * Tries to set the given parameter values. This method should be invoked only when completing parameters
+         * without explicit values. This approach complete the work done in {@code DefaultMathTransformFactory},
+         * which already completed the {@code src_semi_major}, {@code src_semi_minor}, {@code tgt_semi_major} and
+         * {@code tgt_semi_minor} parameters.
+         *
+         * @param  values     the group in which to set the parameter values.
+         * @param  parameter  descriptor of the parameter to set.
+         * @param  value      the new value.
+         * @param  unit       unit of measurement for the new value.
+         */
+        private static void setIfPresent(final Parameters values, final ParameterDescriptor<Double> parameter,
+                final double value, final Unit<?> unit)
+        {
+            try {
+                values.getOrCreate(parameter).setValue(value, unit);
+            } catch (ParameterNotFoundException | InvalidParameterValueException e) {
+                // Nonn-fatal since this attempt was only for information purpose.
+                Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
+                        Molodensky.class, "createMathTransform", e);
+            }
+        }
+
         /** Returns Δa as specified in the parameters if possible, or compute it otherwise. */
         @Override public double semiMajorAxisDifference(final org.opengis.referencing.datum.Ellipsoid target) {
             return (target == other && !Double.isNaN(Δa)) ? Δa : super.semiMajorAxisDifference(target);

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Providers.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Providers.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Providers.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Providers.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -18,7 +18,7 @@ package org.apache.sis.internal.referenc
 
 import java.util.List;
 import org.opengis.referencing.operation.OperationMethod;
-import org.apache.sis.internal.util.LazySet;
+import org.apache.sis.internal.referencing.LazySet;
 
 
 /**

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ZonedTransverseMercator.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ZonedTransverseMercator.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ZonedTransverseMercator.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ZonedTransverseMercator.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -100,6 +100,17 @@ public final class ZonedTransverseMercat
     }
 
     /**
+     * Notifies {@code DefaultMathTransformFactory} that this projection requires
+     * values for the {@code "semi_major"} and {@code "semi_minor"} parameters.
+     *
+     * @return 1, meaning that the operation requires a source ellipsoid.
+     */
+    @Override
+    public final int getEllipsoidsMask() {
+        return 1;
+    }
+
+    /**
      * Creates a map projection from the specified group of parameter values.
      *
      * @param  factory     the factory to use for creating and concatenating the (de)normalization transforms.

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -473,7 +473,7 @@ public class DefaultParameterValue<T> ex
      * {@link URI}, {@link URL}, {@link Path}, {@link File}.
      *
      * @return the reference to a file containing parameter values.
-     * @throws InvalidParameterTypeException if the value is not a reference to a file or an URI.
+     * @throws InvalidParameterTypeException if the value is not a reference to a file or a URI.
      * @throws IllegalStateException if the value is not defined and there is no default value.
      *
      * @see #getValue()



Mime
View raw message