sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1828114 - in /sis/trunk: ./ core/sis-referencing/src/main/java/org/apache/sis/referencing/ core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/ core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/
Date Sat, 31 Mar 2018 15:52:49 GMT
Author: desruisseaux
Date: Sat Mar 31 15:52:49 2018
New Revision: 1828114

URL: http://svn.apache.org/viewvc?rev=1828114&view=rev
Log:
Merge from JDK8 branch.

Added:
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationSorter.java
      - copied unchanged from r1828113, sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationSorter.java
Modified:
    sis/trunk/   (props changed)
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationContext.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/package-info.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java

Propchange: sis/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Sat Mar 31 15:52:49 2018
@@ -2,5 +2,5 @@
 /sis/branches/ISO-19115-3:1804459-1825252
 /sis/branches/JDK6:1394364-1758914
 /sis/branches/JDK7:1394913-1822221
-/sis/branches/JDK8:1584960-1828100
+/sis/branches/JDK8:1584960-1828113
 /sis/branches/JDK9:1773327-1803064

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=1828114&r1=1828113&r2=1828114&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] Sat Mar 31 15:52:49 2018
@@ -131,7 +131,7 @@ import org.apache.sis.util.Static;
  * </ul>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
@@ -621,14 +621,7 @@ public final class CRS extends Static {
     {
         ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS);
         ArgumentChecks.ensureNonNull("targetCRS", targetCRS);
-        CoordinateOperationContext context = null;
-        if (areaOfInterest != null) {
-            if (areaOfInterest instanceof DefaultGeographicBoundingBox && ((DefaultGeographicBoundingBox) areaOfInterest).isEmpty()) {
-                throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "areaOfInterest"));
-            }
-            context = new CoordinateOperationContext();
-            context.setAreaOfInterest(areaOfInterest);
-        }
+        final CoordinateOperationContext context = CoordinateOperationContext.fromBoundingBox(areaOfInterest);
         /*
          * In principle we should just delegate to factory.createOperation(…). However this operation may fail
          * if a connection to the EPSG database has been found, but the EPSG tables do not yet exist in that
@@ -640,12 +633,53 @@ public final class CRS extends Static {
         } catch (UnavailableFactoryException e) {
             if (AuthorityFactories.failure(e)) {
                 throw e;
-            } try {
+            } else try {
                 // Above method call replaced the EPSG factory by a fallback. Try again.
                 return factory.createOperation(sourceCRS, targetCRS, context);
             } catch (FactoryException ex) {
                 ex.addSuppressed(e);
                 throw ex;
+            }
+        }
+    }
+
+    /**
+     * Finds mathematical operations that transform or convert coordinates from the given source to the
+     * given target coordinate reference system. If at least one operation exists, they are returned in
+     * preference order: the operation having the widest intersection between its
+     * {@linkplain AbstractCoordinateOperation#getDomainOfValidity() domain of validity}
+     * and the given area of interest are returned first.
+     *
+     * @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 mathematical operations from {@code sourceCRS} to {@code targetCRS}.
+     * @throws OperationNotFoundException if no operation was found between the given pair of CRS.
+     * @throws FactoryException if the operation can not be created for another reason.
+     *
+     * @see DefaultCoordinateOperationFactory#createOperations(CoordinateReferenceSystem, CoordinateReferenceSystem, CoordinateOperationContext)
+     *
+     * @since 1.0
+     */
+    public static List<CoordinateOperation> findOperations(final CoordinateReferenceSystem sourceCRS,
+                                                           final CoordinateReferenceSystem targetCRS,
+                                                           final GeographicBoundingBox areaOfInterest)
+            throws FactoryException
+    {
+        ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS);
+        ArgumentChecks.ensureNonNull("targetCRS", targetCRS);
+        final CoordinateOperationContext context = CoordinateOperationContext.fromBoundingBox(areaOfInterest);
+        final DefaultCoordinateOperationFactory factory = CoordinateOperations.factory();
+        try {
+            return factory.createOperations(sourceCRS, targetCRS, context);
+        } catch (UnavailableFactoryException e) {
+            if (AuthorityFactories.failure(e)) {
+                throw e;
+            } else try {
+                return Collections.singletonList(factory.createOperation(sourceCRS, targetCRS, context));
+            } catch (FactoryException ex) {
+                ex.addSuppressed(e);
+                throw ex;
             }
         }
     }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationContext.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationContext.java?rev=1828114&r1=1828113&r2=1828114&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationContext.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationContext.java [UTF-8] Sat Mar 31 15:52:49 2018
@@ -21,10 +21,12 @@ import java.util.function.Predicate;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.operation.CoordinateOperation;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
 
 
 /**
@@ -52,7 +54,7 @@ import org.apache.sis.util.ArgumentCheck
  * late binding implementations.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.0
  * @since   0.7
  * @module
  *
@@ -96,6 +98,27 @@ public class CoordinateOperationContext
     }
 
     /**
+     * Creates an operation context for the given area of interest, which may be null.
+     * This is a convenience method for a frequently-used operation.
+     *
+     * @param  areaOfInterest  the area of interest, or {@code null} if none.
+     * @return the operation context, or {@code null} if the given bounding box was null.
+     *
+     * @since 1.0
+     */
+    public static CoordinateOperationContext fromBoundingBox(final GeographicBoundingBox areaOfInterest) {
+        if (areaOfInterest != null) {
+            if (areaOfInterest instanceof DefaultGeographicBoundingBox && ((DefaultGeographicBoundingBox) areaOfInterest).isEmpty()) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "areaOfInterest"));
+            }
+            final CoordinateOperationContext context = new CoordinateOperationContext();
+            context.setAreaOfInterest(areaOfInterest);
+            return context;
+        }
+        return null;
+    }
+
+    /**
      * Returns the spatio-temporal area of interest, or {@code null} if none.
      *
      * @return the spatio-temporal area of interest, or {@code null} if none.

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java?rev=1828114&r1=1828113&r2=1828114&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java [UTF-8] Sat Mar 31 15:52:49 2018
@@ -19,7 +19,9 @@ package org.apache.sis.referencing.opera
 import java.util.Map;
 import java.util.HashMap;
 import java.util.List;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.ListIterator;
 import javax.measure.Unit;
 import javax.measure.quantity.Time;
 import javax.measure.IncommensurableException;
@@ -108,7 +110,7 @@ import static org.apache.sis.util.Utilit
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @see DefaultCoordinateOperationFactory#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem, CoordinateOperationContext)
  *
@@ -165,7 +167,39 @@ public class CoordinateOperationFinder e
     /**
      * Infers an operation for conversion or transformation between two coordinate reference systems.
      * If a non-null authority factory – the <cite>registry</cite> – has been specified at construction time,
-     * this method will first query that factory (<cite>late-binding</cite> approach – see class javadoc).
+     * then this method will first query that factory (<cite>late-binding</cite> approach – see class javadoc).
+     * If no operation has been found in the registry or if no registry has been specified to the constructor,
+     * this method inspects the given CRS and delegates the work to one or many {@code createOperationStep(…)}
+     * methods (<cite>early-binding</cite> approach).
+     *
+     * <p>The default implementation invokes <code>{@linkplain #createOperations createOperations}(sourceCRS,
+     * targetCRS)</code>, then returns the first operation in the returned list or throws an exception if the
+     * list is empty.</p>
+     *
+     * @param  sourceCRS  input coordinate reference system.
+     * @param  targetCRS  output coordinate reference system.
+     * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
+     * @throws OperationNotFoundException if no operation path was found from {@code sourceCRS} to {@code targetCRS}.
+     * @throws FactoryException if the operation creation failed for some other reason.
+     */
+    public CoordinateOperation createOperation(final CoordinateReferenceSystem sourceCRS,
+                                               final CoordinateReferenceSystem targetCRS)
+            throws OperationNotFoundException, FactoryException
+    {
+        final boolean oldState = stopAtFirst;
+        stopAtFirst = true;
+        final List<CoordinateOperation> operations = createOperations(sourceCRS, targetCRS);
+        stopAtFirst = oldState;
+        if (!operations.isEmpty()) {
+            return operations.get(0);
+        }
+        throw new OperationNotFoundException(notFoundMessage(sourceCRS, targetCRS));
+    }
+
+    /**
+     * Infers operations for conversions or transformations between two coordinate reference systems.
+     * If a non-null authority factory – the <cite>registry</cite> – has been specified at construction time,
+     * then this method will first query that factory (<cite>late-binding</cite> approach – see class javadoc).
      * If no operation has been found in the registry or if no registry has been specified to the constructor,
      * this method inspects the given CRS and delegates the work to one or many {@code createOperationStep(…)}
      * methods (<cite>early-binding</cite> approach).
@@ -177,22 +211,29 @@ public class CoordinateOperationFinder e
      * for example in order to process the {@linkplain org.apache.sis.referencing.crs.DefaultProjectedCRS#getBaseCRS()
      * base geographic CRS} of a projected CRS.</p>
      *
+     * <p>Coordinate operations are returned in preference order: best operations for the area of interest should be first.
+     * The returned list is modifiable: callers can add, remove or set elements without impact on this
+     * {@code CoordinateOperationFinder} instance.</p>
+     *
      * @param  sourceCRS  input coordinate reference system.
      * @param  targetCRS  output coordinate reference system.
-     * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
+     * @return coordinate operations from {@code sourceCRS} to {@code targetCRS}.
      * @throws OperationNotFoundException if no operation path was found from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation creation failed for some other reason.
+     *
+     * @since 1.0
      */
     @Override
-    public CoordinateOperation createOperation(final CoordinateReferenceSystem sourceCRS,
-                                               final CoordinateReferenceSystem targetCRS)
-            throws OperationNotFoundException, FactoryException
+    public List<CoordinateOperation> createOperations(final CoordinateReferenceSystem sourceCRS,
+                                                      final CoordinateReferenceSystem targetCRS)
+            throws FactoryException
     {
         ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS);
         ArgumentChecks.ensureNonNull("targetCRS", targetCRS);
         if (equalsIgnoreMetadata(sourceCRS, targetCRS)) try {
-            return createFromAffineTransform(AXIS_CHANGES, sourceCRS, targetCRS,
-                    CoordinateSystems.swapAndScaleAxes(sourceCRS.getCoordinateSystem(), targetCRS.getCoordinateSystem()));
+            return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS, targetCRS,
+                            CoordinateSystems.swapAndScaleAxes(sourceCRS.getCoordinateSystem(),
+                                                               targetCRS.getCoordinateSystem())));
         } catch (IllegalArgumentException | IncommensurableException e) {
             throw new FactoryException(Resources.format(Resources.Keys.CanNotInstantiateGeodeticObject_1, new CRSPair(sourceCRS, targetCRS)), e);
         }
@@ -204,27 +245,15 @@ public class CoordinateOperationFinder e
          * is not in the cache, store the key in our internal map for preventing infinite recursivity.
          */
         final CRSPair key = new CRSPair(sourceCRS, targetCRS);
-        if (useCache && !previousSearches.isEmpty()) {
+        if (useCache && stopAtFirst && !previousSearches.isEmpty()) {
             final CoordinateOperation op = factorySIS.cache.peek(key);
-            if (op != null) return op;
+            if (op != null) return asList(op);      // Must be a modifiable list as per this method contract.
         }
         if (previousSearches.put(key, Boolean.TRUE) != null) {
             throw new FactoryException(Resources.format(Resources.Keys.RecursiveCreateCallForCode_2, CoordinateOperation.class, key));
         }
         /*
-         * Verify if some extension module handles this pair of CRS in a special way. For example it may
-         * be the "sis-gdal" module checking if the given CRS are wrappers around Proj.4 data structure.
-         */
-        for (final SpecializedOperationFactory sp : factorySIS.getSpecializedFactories()) {
-            for (final CoordinateOperation op : sp.findOperations(sourceCRS, targetCRS)) {
-                if (filter(op)) {
-                    return op;
-                }
-            }
-        }
-        /*
          * If the user did not specified an area of interest, use the domain of validity of the CRS.
-         * Then verify in the EPSG dataset if the operation is explicitely defined by an authority.
          */
         GeographicBoundingBox bbox = Extents.getGeographicBoundingBox(areaOfInterest);
         if (bbox == null) {
@@ -232,9 +261,30 @@ public class CoordinateOperationFinder e
                                         CRS.getGeographicBoundingBox(targetCRS));
             areaOfInterest = CoordinateOperationContext.setGeographicBoundingBox(areaOfInterest, bbox);
         }
+        /*
+         * Verify if some extension module handles this pair of CRS in a special way. For example it may
+         * be the "sis-gdal" module checking if the given CRS are wrappers around Proj.4 data structure.
+         */
+        {   // For keeping 'operations' list locale.
+            final List<CoordinateOperation> operations = new ArrayList<>();
+            for (final SpecializedOperationFactory sp : factorySIS.getSpecializedFactories()) {
+                for (final CoordinateOperation op : sp.findOperations(sourceCRS, targetCRS)) {
+                    if (filter(op)) {
+                        operations.add(op);
+                    }
+                }
+            }
+            if (!operations.isEmpty()) {
+                CoordinateOperationSorter.sort(operations, bbox);
+                return operations;
+            }
+        }
+        /*
+         * Verify in the EPSG dataset if the operation is explicitely defined by an authority.
+         */
         if (registry != null) {
-            final CoordinateOperation op = super.createOperation(sourceCRS, targetCRS);
-            if (op != null) return op;
+            final List<CoordinateOperation> authoritatives = super.createOperations(sourceCRS, targetCRS);
+            if (!authoritatives.isEmpty()) return authoritatives;
         }
         ////////////////////////////////////////////////////////////////////////////////
         ////                                                                        ////
@@ -310,7 +360,7 @@ public class CoordinateOperationFinder e
     }
 
     /**
-     * Creates an operation from an arbitrary single CRS to a derived coordinate reference system.
+     * Creates operations from an arbitrary single CRS to a derived coordinate reference system.
      * Conversions from {@code GeographicCRS} to {@code ProjectedCRS} are also handled by this method,
      * since projected CRS are a special kind of {@code GeneralDerivedCRS}.
      *
@@ -320,18 +370,29 @@ public class CoordinateOperationFinder e
      * where the conversion from {@code baseCRS} to {@code targetCRS} is obtained from
      * <code>targetCRS.{@linkplain GeneralDerivedCRS#getConversionFromBase() getConversionFromBase()}</code>.
      *
+     * <p>This method returns only <em>one</em> step for a chain of concatenated operations (to be built by the caller).
+     * But a list is returned because the same step may be implemented by different operation methods. Only one element
+     * in the returned list should be selected (usually the first one).</p>
+     *
      * @param  sourceCRS  input coordinate reference system.
      * @param  targetCRS  output coordinate reference system.
-     * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
+     * @return coordinate operations from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation can not be constructed.
      */
-    protected CoordinateOperation createOperationStep(final SingleCRS sourceCRS,
-                                                      final GeneralDerivedCRS targetCRS)
+    protected List<CoordinateOperation> createOperationStep(final SingleCRS sourceCRS,
+                                                            final GeneralDerivedCRS targetCRS)
             throws FactoryException
     {
-        final CoordinateOperation step1 = createOperation(sourceCRS, targetCRS.getBaseCRS());
-        final CoordinateOperation step2 = targetCRS.getConversionFromBase();
-        return concatenate(step1, step2);
+        final List<CoordinateOperation> operations = createOperations(sourceCRS, targetCRS.getBaseCRS());
+        final ListIterator<CoordinateOperation> it = operations.listIterator();
+        if (it.hasNext()) {
+            final CoordinateOperation step2 = targetCRS.getConversionFromBase();
+            do {
+                final CoordinateOperation step1 = it.next();
+                it.set(concatenate(step1, step2));
+            } while (it.hasNext());
+        }
+        return operations;
     }
 
     /**
@@ -345,26 +406,36 @@ public class CoordinateOperationFinder e
      * where the conversion from {@code sourceCRS} to {@code baseCRS} is obtained from the inverse of
      * <code>sourceCRS.{@linkplain GeneralDerivedCRS#getConversionFromBase() getConversionFromBase()}</code>.
      *
+     * <p>This method returns only <em>one</em> step for a chain of concatenated operations (to be built by the caller).
+     * But a list is returned because the same step may be implemented by different operation methods. Only one element
+     * in the returned list should be selected (usually the first one).</p>
+     *
      * @param  sourceCRS  input coordinate reference system.
      * @param  targetCRS  output coordinate reference system.
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation can not be constructed.
      */
-    protected CoordinateOperation createOperationStep(final GeneralDerivedCRS sourceCRS,
-                                                      final SingleCRS targetCRS)
+    protected List<CoordinateOperation> createOperationStep(final GeneralDerivedCRS sourceCRS,
+                                                            final SingleCRS targetCRS)
             throws FactoryException
     {
-        // Create first the operation that is more at risk to fail.
-        final CoordinateOperation step2 = createOperation(sourceCRS.getBaseCRS(), targetCRS);
-        final CoordinateOperation step1;
-        try {
-            step1 = inverse(sourceCRS.getConversionFromBase());
-        } catch (OperationNotFoundException exception) {
-            throw exception;
-        } catch (FactoryException | NoninvertibleTransformException exception) {
-            throw new OperationNotFoundException(canNotInvert(sourceCRS), exception);
+        final List<CoordinateOperation> operations = createOperations(sourceCRS.getBaseCRS(), targetCRS);
+        final ListIterator<CoordinateOperation> it = operations.listIterator();
+        if (it.hasNext()) {
+            final CoordinateOperation step1;
+            try {
+                step1 = inverse(sourceCRS.getConversionFromBase());
+            } catch (OperationNotFoundException exception) {
+                throw exception;
+            } catch (FactoryException | NoninvertibleTransformException exception) {
+                throw new OperationNotFoundException(canNotInvert(sourceCRS), exception);
+            }
+            do {
+                final CoordinateOperation step2 = it.next();
+                it.set(concatenate(step1, step2));
+            } while (it.hasNext());
         }
-        return concatenate(step1, step2);
+        return operations;
     }
 
     /**
@@ -377,27 +448,37 @@ public class CoordinateOperationFinder e
      *   <li>Convert from the target base CRS to the {@code targetCRS}.</li>
      * </ol>
      *
+     * <p>This method returns only <em>one</em> step for a chain of concatenated operations (to be built by the caller).
+     * But a list is returned because the same step may be implemented by different operation methods. Only one element
+     * in the returned list should be selected (usually the first one).</p>
+     *
      * @param  sourceCRS  input coordinate reference system.
      * @param  targetCRS  output coordinate reference system.
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation can not be constructed.
      */
-    protected CoordinateOperation createOperationStep(final GeneralDerivedCRS sourceCRS,
-                                                      final GeneralDerivedCRS targetCRS)
+    protected List<CoordinateOperation> createOperationStep(final GeneralDerivedCRS sourceCRS,
+                                                            final GeneralDerivedCRS targetCRS)
             throws FactoryException
     {
-        // Create first the operations that are more at risk to fail.
-        final CoordinateOperation step2 = createOperation(sourceCRS.getBaseCRS(), targetCRS.getBaseCRS());
-        final CoordinateOperation step3 = targetCRS.getConversionFromBase();
-        final CoordinateOperation step1;
-        try {
-            step1 = inverse(sourceCRS.getConversionFromBase());
-        } catch (OperationNotFoundException exception) {
-            throw exception;
-        } catch (FactoryException | NoninvertibleTransformException exception) {
-            throw new OperationNotFoundException(canNotInvert(sourceCRS), exception);
+        final List<CoordinateOperation> operations = createOperations(sourceCRS.getBaseCRS(), targetCRS.getBaseCRS());
+        final ListIterator<CoordinateOperation> it = operations.listIterator();
+        if (it.hasNext()) {
+            final CoordinateOperation step3 = targetCRS.getConversionFromBase();
+            final CoordinateOperation step1;
+            try {
+                step1 = inverse(sourceCRS.getConversionFromBase());
+            } catch (OperationNotFoundException exception) {
+                throw exception;
+            } catch (FactoryException | NoninvertibleTransformException exception) {
+                throw new OperationNotFoundException(canNotInvert(sourceCRS), exception);
+            }
+            do {
+                final CoordinateOperation step2 = it.next();
+                it.set(concatenate(step1, step2, step3));
+            } while (it.hasNext());
         }
-        return concatenate(step1, step2, step3);
+        return operations;
     }
 
     /**
@@ -413,14 +494,18 @@ public class CoordinateOperationFinder e
      *       for the area of interest.</li>
      * </ul>
      *
+     * <p>This method returns only <em>one</em> step for a chain of concatenated operations (to be built by the caller).
+     * But a list is returned because the same step may be implemented by different operation methods. Only one element
+     * in the returned list should be selected (usually the first one).</p>
+     *
      * @param  sourceCRS  input coordinate reference system.
      * @param  targetCRS  output coordinate reference system.
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation can not be constructed.
      */
     @SuppressWarnings("null")
-    protected CoordinateOperation createOperationStep(final GeodeticCRS sourceCRS,
-                                                      final GeodeticCRS targetCRS)
+    protected List<CoordinateOperation> createOperationStep(final GeodeticCRS sourceCRS,
+                                                            final GeodeticCRS targetCRS)
             throws FactoryException
     {
         final GeodeticDatum sourceDatum = sourceCRS.getDatum();
@@ -577,7 +662,7 @@ public class CoordinateOperationFinder e
                 transform = mtFactory.createConcatenatedTransform(transform, after);
             }
         }
-        return createFromMathTransform(properties(identifier), sourceCRS, targetCRS, transform, method, parameters, null);
+        return asList(createFromMathTransform(properties(identifier), sourceCRS, targetCRS, transform, method, parameters, null));
     }
 
     /**
@@ -585,13 +670,17 @@ public class CoordinateOperationFinder e
      * The height returned by this method will usually be part of a
      * {@linkplain DefaultPassThroughOperation pass-through operation}.
      *
+     * <p>This method returns only <em>one</em> step for a chain of concatenated operations (to be built by the caller).
+     * But a list is returned because the same step may be implemented by different operation methods. Only one element
+     * in the returned list should be selected (usually the first one).</p>
+     *
      * @param  sourceCRS  input coordinate reference system.
      * @param  targetCRS  output coordinate reference system.
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation can not be constructed.
      */
-    protected CoordinateOperation createOperationStep(final GeodeticCRS sourceCRS,
-                                                      final VerticalCRS targetCRS)
+    protected List<CoordinateOperation> createOperationStep(final GeodeticCRS sourceCRS,
+                                                            final VerticalCRS targetCRS)
             throws FactoryException
     {
         /*
@@ -676,7 +765,7 @@ public class CoordinateOperationFinder e
         matrix.setElement(0,      i,      1);                                       // Scale factor for height.
         matrix.setElement(tgtDim, srcDim, 1);                                       // Always 1 for affine transform.
         step2 = createFromAffineTransform(AXIS_CHANGES, interpolationCRS, heightCRS, matrix);
-        return concatenate(step1, step2, step3);
+        return asList(concatenate(step1, step2, step3));
     }
 
     /**
@@ -684,6 +773,10 @@ public class CoordinateOperationFinder e
      * The default implementation checks if both CRS use the same datum, then
      * adjusts for axis direction and units.
      *
+     * <p>This method returns only <em>one</em> step for a chain of concatenated operations (to be built by the caller).
+     * But a list is returned because the same step may be implemented by different operation methods. Only one element
+     * in the returned list should be selected (usually the first one).</p>
+     *
      * @param  sourceCRS  input coordinate reference system.
      * @param  targetCRS  output coordinate reference system.
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
@@ -691,8 +784,8 @@ public class CoordinateOperationFinder e
      *
      * @todo Needs to implement vertical datum shift.
      */
-    protected CoordinateOperation createOperationStep(final VerticalCRS sourceCRS,
-                                                      final VerticalCRS targetCRS)
+    protected List<CoordinateOperation> createOperationStep(final VerticalCRS sourceCRS,
+                                                            final VerticalCRS targetCRS)
             throws FactoryException
     {
         final VerticalDatum sourceDatum = sourceCRS.getDatum();
@@ -708,7 +801,7 @@ public class CoordinateOperationFinder e
         } catch (IllegalArgumentException | IncommensurableException exception) {
             throw new OperationNotFoundException(notFoundMessage(sourceCRS, targetCRS), exception);
         }
-        return createFromAffineTransform(AXIS_CHANGES, sourceCRS, targetCRS, matrix);
+        return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS, targetCRS, matrix));
     }
 
     /**
@@ -716,13 +809,17 @@ public class CoordinateOperationFinder e
      * The default implementation checks if both CRS use the same datum, then
      * adjusts for axis direction, units and epoch.
      *
+     * <p>This method returns only <em>one</em> step for a chain of concatenated operations (to be built by the caller).
+     * But a list is returned because the same step may be implemented by different operation methods. Only one element
+     * in the returned list should be selected (usually the first one).</p>
+     *
      * @param  sourceCRS  input coordinate reference system.
      * @param  targetCRS  output coordinate reference system.
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation can not be constructed.
      */
-    protected CoordinateOperation createOperationStep(final TemporalCRS sourceCRS,
-                                                      final TemporalCRS targetCRS)
+    protected List<CoordinateOperation> createOperationStep(final TemporalCRS sourceCRS,
+                                                            final TemporalCRS targetCRS)
             throws FactoryException
     {
         final TemporalDatum sourceDatum = sourceCRS.getDatum();
@@ -757,7 +854,7 @@ public class CoordinateOperationFinder e
         final int translationColumn = matrix.getNumCol() - 1;           // Paranoiac check: should always be 1.
         final double translation = matrix.getElement(0, translationColumn);
         matrix.setElement(0, translationColumn, translation + epochShift);
-        return createFromAffineTransform(AXIS_CHANGES, sourceCRS, targetCRS, matrix);
+        return asList(createFromAffineTransform(AXIS_CHANGES, sourceCRS, targetCRS, matrix));
     }
 
     /**
@@ -766,6 +863,10 @@ public class CoordinateOperationFinder e
      * various combinations of source and target components. A preference is given for components of the same
      * type (e.g. source {@link GeodeticCRS} with target {@code GeodeticCRS}, <i>etc.</i>).
      *
+     * <p>This method returns only <em>one</em> step for a chain of concatenated operations (to be built by the caller).
+     * But a list is returned because the same step may be implemented by different operation methods. Only one element
+     * in the returned list should be selected (usually the first one).</p>
+     *
      * @param  sourceCRS         input coordinate reference system.
      * @param  sourceComponents  components of the source CRS.
      * @param  targetCRS         output coordinate reference system.
@@ -773,7 +874,7 @@ public class CoordinateOperationFinder e
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation can not be constructed.
      */
-    protected CoordinateOperation createOperationStep(
+    protected List<CoordinateOperation> createOperationStep(
             final CoordinateReferenceSystem sourceCRS, final List<? extends SingleCRS> sourceComponents,
             final CoordinateReferenceSystem targetCRS, final List<? extends SingleCRS> targetComponents)
             throws FactoryException
@@ -893,7 +994,7 @@ public class CoordinateOperationFinder e
             endAtDimension -= delta;
             remainingSourceDimensions -= delta;
         }
-        return operation;
+        return asList(operation);
     }
 
 
@@ -1103,6 +1204,17 @@ public class CoordinateOperationFinder e
     }
 
     /**
+     * Returns the given operation as a list of one element. We can not use {@link Collections#singletonList(Object)}
+     * because the list needs to be modifiable, as required by {@link #createOperations(CoordinateReferenceSystem,
+     * CoordinateReferenceSystem)} method contract.
+     */
+    private static List<CoordinateOperation> asList(final CoordinateOperation operation) {
+        final List<CoordinateOperation> operations = new ArrayList<>(1);
+        operations.add(operation);
+        return operations;
+    }
+
+    /**
      * Returns an error message for "No path found from sourceCRS to targetCRS".
      * This is used for the construction of {@link OperationNotFoundException}.
      *

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java?rev=1828114&r1=1828113&r2=1828114&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java [UTF-8] Sat Mar 31 15:52:49 2018
@@ -22,7 +22,7 @@ import java.util.List;
 import java.util.ListIterator;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Iterator;
+import java.util.Collections;
 import java.util.Objects;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
@@ -94,7 +94,7 @@ import org.apache.sis.util.resources.Voc
  *       That approach is used only as a fallback when the late-binding approach gave no result.</li>
  * </ul>
  *
- * When <code>{@linkplain #createOperation createOperation}(sourceCRS, targetCRS)</code> is invoked,
+ * When <code>{@linkplain #createOperations createOperations}(sourceCRS, targetCRS)</code> is invoked,
  * this class fetches the authority codes for source and target CRS and submits them to the authority factory
  * through a call to its <code>{@linkplain GeodeticAuthorityFactory#createFromCoordinateReferenceSystemCodes
  * createFromCoordinateReferenceSystemCodes}(sourceCode, targetCode)</code> method.
@@ -102,7 +102,7 @@ import org.apache.sis.util.resources.Voc
  * then {@link CoordinateOperationFinder} will use its own fallback.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.7
  * @module
  */
@@ -193,6 +193,13 @@ class CoordinateOperationRegistry {
     protected double desiredAccuracy;
 
     /**
+     * {@code true} if {@link #search(CoordinateReferenceSystem, CoordinateReferenceSystem)}
+     * should stop after the first coordinate operation found. This field is set to {@code true}
+     * when the caller is interested only in the "best" operation instead of all of possibilities.
+     */
+    boolean stopAtFirst;
+
+    /**
      * A filter that can be used for applying additional restrictions on the coordinate operation,
      * or {@code null} if none.
      */
@@ -279,29 +286,26 @@ class CoordinateOperationRegistry {
     }
 
     /**
-     * Finds or infers an operation for conversion or transformation between two coordinate reference systems.
+     * Finds or infers operations for conversions or transformations between two coordinate reference systems.
      * {@code CoordinateOperationRegistry} implements the <cite>late-binding</cite> approach (see definition
      * of terms in class javadoc) by extracting the authority codes from the supplied {@code sourceCRS} and
      * {@code targetCRS}, then by submitting those codes to the
      * <code>{@linkplain CoordinateOperationAuthorityFactory#createFromCoordinateReferenceSystemCodes
      * createFromCoordinateReferenceSystemCodes}(sourceCode, targetCode)</code> method.
-     * If no operation is found for those codes, then this method returns {@code null}.
-     * Note that it does not mean that no path exist;
-     * it only means that it was not defined explicitely in the registry.
      *
-     * <p>If the subclass implements the <cite>early-binding</cite> approach (which is the fallback if late-binding
-     * gave no result), then this method should never return {@code null} since there is no other fallback.
-     * Instead, this method may throw an {@link OperationNotFoundException}.</p>
+     * <p>Operations in the returned list are ordered in preference order (preferred operation first).
+     * If no operation is found for those codes, then this method returns an empty list.
+     * Note that it does not mean that no path exist;
+     * it only means that it was not defined explicitely in the registry.</p>
      *
      * @param  sourceCRS  input coordinate reference system.
      * @param  targetCRS  output coordinate reference system.
-     * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS},
-     *         or {@code null} if no such operation is explicitly defined in the underlying database.
-     * @throws OperationNotFoundException if no operation path was found from {@code sourceCRS} to {@code targetCRS}.
-     * @throws FactoryException if the operation creation failed for some other reason.
+     * @return coordinate operations from {@code sourceCRS} to {@code targetCRS},
+     *         or an empty list if no such operation is explicitly defined in the underlying database.
+     * @throws FactoryException if the operation creation failed.
      */
-    public CoordinateOperation createOperation(final CoordinateReferenceSystem sourceCRS,
-                                               final CoordinateReferenceSystem targetCRS)
+    public List<CoordinateOperation> createOperations(final CoordinateReferenceSystem sourceCRS,
+                                                      final CoordinateReferenceSystem targetCRS)
             throws FactoryException
     {
         CoordinateReferenceSystem source = sourceCRS;
@@ -324,24 +328,31 @@ class CoordinateOperationRegistry {
                 case 3:  if (source == sourceCRS || target == targetCRS) continue;
                          target = targetCRS;                                            // 2D → 3D
                          break;
-                default: return null;
+                default: return Collections.emptyList();
             }
             if (source != null && target != null) try {
-                CoordinateOperation operation = search(source, target);
-                if (operation != null) {
+                final List<CoordinateOperation> operations = search(source, target);
+                if (operations != null) {
                     /*
                      * Found an operation. If we had to extract the horizontal part of some 3D CRS, then we
                      * need to modify the coordinate operation in order to match the new number of dimensions.
                      */
                     if (combine != 0) {
-                        operation = propagateVertical(sourceCRS, source != sourceCRS,
-                                                      targetCRS, target != targetCRS, operation);
-                        if (operation == null) {
-                            continue;
+                        for (int i=operations.size(); --i >= 0;) {
+                            CoordinateOperation operation = operations.get(i);
+                            operation = propagateVertical(sourceCRS, source != sourceCRS,
+                                                          targetCRS, target != targetCRS, operation);
+                            if (operation != null) {
+                                operation = complete(operation, sourceCRS, targetCRS);
+                                operations.set(i, operation);
+                            } else {
+                                operations.remove(i);
+                            }
                         }
-                        operation = complete(operation, sourceCRS, targetCRS);
                     }
-                    return operation;
+                    if (!operations.isEmpty()) {
+                        return operations;
+                    }
                 }
             } catch (IllegalArgumentException | IncommensurableException e) {
                 String message = Resources.format(Resources.Keys.CanNotInstantiateGeodeticObject_1, new CRSPair(sourceCRS, targetCRS));
@@ -355,7 +366,7 @@ class CoordinateOperationRegistry {
     }
 
     /**
-     * Returns an operation for conversion or transformation between two coordinate reference systems.
+     * Returns operations for conversions or transformations between two coordinate reference systems.
      * This method extracts the authority code from the supplied {@code sourceCRS} and {@code targetCRS},
      * and submit them to the {@link #registry}. If no operation is found for those codes, then this method
      * returns {@code null}.
@@ -368,14 +379,15 @@ class CoordinateOperationRegistry {
      * @throws IncommensurableException if the units are not compatible or a unit conversion is non-linear.
      * @throws FactoryException if an error occurred while creating the operation.
      */
-    private CoordinateOperation search(final CoordinateReferenceSystem sourceCRS,
-                                       final CoordinateReferenceSystem targetCRS)
+    private List<CoordinateOperation> search(final CoordinateReferenceSystem sourceCRS,
+                                             final CoordinateReferenceSystem targetCRS)
             throws IllegalArgumentException, IncommensurableException, FactoryException
     {
         final List<String> sources = findCode(sourceCRS); if (sources.isEmpty()) return null;
         final List<String> targets = findCode(targetCRS); if (targets.isEmpty()) return null;
-        Collection<CoordinateOperation> operations = null;
-        boolean inverse = false;
+        final List<CoordinateOperation> operations = new ArrayList<>();
+        boolean foundDirectOperations = false;
+        boolean useDeprecatedOperations = false;
         for (final String sourceID : sources) {
             for (final String targetID : targets) {
                 if (sourceID.equals(targetID)) {
@@ -390,105 +402,82 @@ class CoordinateOperationRegistry {
                      */
                     return null;
                 }
+                /*
+                 * Some pairs of CRS have a lot of coordinate operations backed by datum shift grids.
+                 * We do not want to load all of them until we found the right coordinate operation.
+                 * The non-public Semaphores.METADATA_ONLY mechanism instructs EPSGDataAccess to
+                 * instantiate DeferredCoordinateOperation instead of full coordinate operations.
+                 */
                 final boolean mdOnly = Semaphores.queryAndSet(Semaphores.METADATA_ONLY);
                 try {
+                    Collection<CoordinateOperation> authoritatives;
                     try {
-                        operations = registry.createFromCoordinateReferenceSystemCodes(sourceID, targetID);
-                        inverse = Containers.isNullOrEmpty(operations);
+                        authoritatives = registry.createFromCoordinateReferenceSystemCodes(sourceID, targetID);
+                        final boolean inverse = Containers.isNullOrEmpty(authoritatives);
                         if (inverse) {
                             /*
                              * No operation from 'source' to 'target' available. But maybe there is an inverse operation.
                              * This is typically the case when the user wants to convert from a projected to a geographic CRS.
                              * The EPSG database usually contains transformation paths for geographic to projected CRS only.
                              */
-                            operations = registry.createFromCoordinateReferenceSystemCodes(targetID, sourceID);
-                            if (Containers.isNullOrEmpty(operations)) {
+                            if (foundDirectOperations) {
+                                continue;                       // Ignore inverse operations if we already have direct ones.
+                            }
+                            authoritatives = registry.createFromCoordinateReferenceSystemCodes(targetID, sourceID);
+                            if (Containers.isNullOrEmpty(authoritatives)) {
                                 continue;
                             }
+                        } else if (!foundDirectOperations) {
+                            foundDirectOperations = true;
+                            operations.clear();                 // Keep only direct operations.
                         }
-                    } finally {
-                        if (!mdOnly) {
-                            Semaphores.clear(Semaphores.METADATA_ONLY);
-                        }
+                    } catch (NoSuchAuthorityCodeException | MissingFactoryResourceException e) {
+                        /*
+                         * sourceCode or targetCode is unknown to the underlying authority factory.
+                         * Ignores the exception and fallback on the generic algorithm provided by
+                         * CoordinateOperationFinder.
+                         */
+                        log(null, e);
+                        continue;
                     }
-                } catch (NoSuchAuthorityCodeException | MissingFactoryResourceException e) {
-                    /*
-                     * sourceCode or targetCode is unknown to the underlying authority factory.
-                     * Ignores the exception and fallback on the generic algorithm provided by
-                     * CoordinateOperationFinder.
-                     */
-                    log(null, e);
-                    continue;
-                }
-                break;          // Stop on the first non-empty set of operations that we find.
-            }
-        }
-        if (operations == null) {
-            return null;
-        }
-        /*
-         * This outer loop is executed exactly once in most case. It may be executed more than once if an
-         * ignoreable error occurred while creating the CoordinateOperation, in which case we will fallback
-         * on the next best choice until we succeed.
-         */
-        CoordinateOperation bestChoice;
-        while (true) {
-            /*
-             * We will loop over all coordinate operations and select the one having the largest intersection
-             * with the area of interest. Note that if the user did not specified an area of interest himself,
-             * then we need to get one from the CRS. This is necessary for preventing the transformation from
-             * NAD27 to NAD83 in Idaho to select the transform for Alaska (since the later has a larger area).
-             */
-            bestChoice = null;
-            double largestArea = 0;
-            double finestAccuracy = Double.POSITIVE_INFINITY;
-            boolean stopAtFirstDeprecated = false;
-            for (final Iterator<CoordinateOperation> it=operations.iterator();;) {
-                CoordinateOperation candidate;
-                /*
-                 * Some pair of CRS have a lot of coordinate operations backed by datum shift grids.
-                 * We do not want to load all of them until we found the right coordinate operation.
-                 * The non-public Semaphores.METADATA_ONLY mechanism instructs EPSGDataAccess to
-                 * instantiate DeferredCoordinateOperation instead of full coordinate operations.
-                 */
-                final boolean mdOnly = Semaphores.queryAndSet(Semaphores.METADATA_ONLY);
-                try {
-                    try {
-                        if (!it.hasNext()) break;
-                        candidate = it.next();
-                    } finally {
-                        if (!mdOnly) {
-                            Semaphores.clear(Semaphores.METADATA_ONLY);
-                        }
-                    }
-                } catch (BackingStoreException exception) {
-                    throw exception.unwrapOrRethrow(FactoryException.class);
-                }
-                if (candidate != null) {
                     /*
                      * If we found at least one non-deprecated operation, we will stop the search at
                      * the first deprecated one (assuming that deprecated operations are sorted last).
+                     * Deprecated operations are kept only if there is no non-deprecated operations.
                      */
-                    final boolean isDeprecated = (candidate instanceof Deprecable) && ((Deprecable) candidate).isDeprecated();
-                    if (isDeprecated && stopAtFirstDeprecated) {
-                        break;
-                    }
-                    final double area = Extents.area(Extents.intersection(
-                            Extents.getGeographicBoundingBox(areaOfInterest),
-                            Extents.getGeographicBoundingBox(candidate.getDomainOfValidity())));
-                    if (bestChoice == null || area >= largestArea) {
-                        final double accuracy = CRS.getLinearAccuracy(candidate);
-                        if (bestChoice == null || area != largestArea || accuracy < finestAccuracy) {
-                            bestChoice = candidate;
-                            if (!Double.isNaN(area)) {
-                                largestArea = area;
+                    try {
+                        for (final CoordinateOperation candidate : authoritatives) {
+                            if (candidate != null) {                                    // Paranoiac check.
+                                if ((candidate instanceof Deprecable) && ((Deprecable) candidate).isDeprecated()) {
+                                    if (!useDeprecatedOperations && !operations.isEmpty()) break;
+                                    useDeprecatedOperations = true;
+                                } else if (useDeprecatedOperations) {
+                                    useDeprecatedOperations = false;
+                                    operations.clear();              // Replace deprecated operations by non-deprecated ones.
+                                }
+                                operations.add(candidate);
                             }
-                            finestAccuracy = Double.isNaN(accuracy) ? Double.POSITIVE_INFINITY : accuracy;
-                            stopAtFirstDeprecated = !isDeprecated;
                         }
+                    } catch (BackingStoreException exception) {
+                        throw exception.unwrapOrRethrow(FactoryException.class);
+                    }
+                } finally {
+                    if (!mdOnly) {
+                        Semaphores.clear(Semaphores.METADATA_ONLY);
                     }
                 }
             }
+        }
+        /*
+         * At this point we got the list of coordinate operations. Now, sort them in preference order.
+         * We will loop over all coordinate operations and select the one having the largest intersection
+         * with the area of interest. Note that if the user did not specified an area of interest himself,
+         * then we need to get one from the CRS. This is necessary for preventing the transformation from
+         * NAD27 to NAD83 in Idaho to select the transform for Alaska (since the later has a larger area).
+         */
+        CoordinateOperationSorter.sort(operations, Extents.getGeographicBoundingBox(areaOfInterest));
+        final ListIterator<CoordinateOperation> it = operations.listIterator();
+        while (it.hasNext()) {
             /*
              * At this point we filtered a CoordinateOperation by looking only at its metadata.
              * Code following this point will need the full coordinate operation, including its
@@ -496,45 +485,31 @@ class CoordinateOperationRegistry {
              * Conversely, we should not use metadata below this point because the call to
              * inverse(CoordinateOperation) is not guaranteed to preserve all metadata.
              */
-            if (bestChoice == null) break;
-            final CoordinateOperation deferred = bestChoice;
+            CoordinateOperation operation = it.next();
             try {
-                if (bestChoice instanceof DeferredCoordinateOperation) {
-                    bestChoice = ((DeferredCoordinateOperation) bestChoice).create();
+                if (operation instanceof DeferredCoordinateOperation) {
+                    operation = ((DeferredCoordinateOperation) operation).create();
                 }
-                if (bestChoice instanceof SingleOperation && bestChoice.getMathTransform() == null) {
-                    bestChoice = fromDefiningConversion((SingleOperation) bestChoice,
-                                                        inverse ? targetCRS : sourceCRS,
-                                                        inverse ? sourceCRS : targetCRS);
-                    if (bestChoice == null) {
-                        return null;
+                if (operation instanceof SingleOperation && operation.getMathTransform() == null) {
+                    operation = fromDefiningConversion((SingleOperation) operation,
+                                    foundDirectOperations ? sourceCRS : targetCRS,
+                                    foundDirectOperations ? targetCRS : sourceCRS);
+                    if (operation == null) {
+                        it.remove();
+                        continue;
                     }
                 }
-                if (inverse) {
-                    bestChoice = inverse(bestChoice);
+                if (!foundDirectOperations) {
+                    operation = inverse(operation);
                 }
             } catch (NoninvertibleTransformException | MissingFactoryResourceException e) {
                 /*
-                 * If we failed to get the real CoordinateOperation instance, remove it from the collection
-                 * and try again in order to get the next best choice. The Apache SIS implementation allows
-                 * to remove directly from the Set<CoordinateOperation>, but that removal may fail with non-SIS
-                 * implementations, in which case we copy the CoordinateOperation in a temporary list before
-                 * removal. We do not perform that copy unconditionally in order to avoid lazy initialization
-                 * of unneeded CoordinateOperations like the deprecated ones.
+                 * If we failed to get the real CoordinateOperation instance, remove it from
+                 * the collection and try again in order to get the next best choices.
                  */
-                boolean removed;
-                try {
-                    removed = operations.remove(deferred);
-                } catch (UnsupportedOperationException ignored) {
-                    operations = new ArrayList<>(operations);
-                    removed = operations.remove(deferred);
-                }
-                if (removed) {
-                    log(null, e);
-                    continue;                                   // Try again with the next best case.
-                }
-                // Should never happen, but if happen anyway we should fail for avoiding never-ending loop.
-                throw (e instanceof FactoryException) ? (FactoryException) e : new FactoryException(e);
+                log(null, e);
+                it.remove();
+                continue;                                   // Try again with the next best case.
             }
             /*
              * It is possible that the CRS given to this method were not quite right.  For example the user
@@ -548,10 +523,19 @@ class CoordinateOperationRegistry {
              * If we fail here, we are likely to fail for all other transforms. So we are better to let the
              * FactoryException propagate.
              */
-            bestChoice = complete(bestChoice, sourceCRS, targetCRS);
-            if (filter(bestChoice)) break;
+            operation = complete(operation, sourceCRS, targetCRS);
+            if (filter(operation)) {
+                if (stopAtFirst) {
+                    operations.clear();
+                    operations.add(operation);
+                    break;
+                }
+                it.set(operation);
+            } else {
+                it.remove();
+            }
         }
-        return bestChoice;
+        return operations;
     }
 
     /**
@@ -1204,6 +1188,6 @@ class CoordinateOperationRegistry {
         if (exception instanceof NoninvertibleTransformException) {
             record.setThrown(exception);
         }
-        Logging.log(CoordinateOperationFinder.class, "createOperation", record);
+        Logging.log(CoordinateOperationFinder.class, "createOperations", record);
     }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java?rev=1828114&r1=1828113&r2=1828114&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java [UTF-8] Sat Mar 31 15:52:49 2018
@@ -85,7 +85,7 @@ import org.apache.sis.util.Utilities;
  * The second approach is the most frequently used.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.6
  * @module
  */
@@ -820,6 +820,43 @@ next:   for (int i=components.size(); --
     }
 
     /**
+     * Finds or creates operations for conversions or transformations between two coordinate reference systems.
+     * If at least one operation exists, they are returned in preference order: the operation having the widest
+     * intersection between its {@linkplain AbstractCoordinateOperation#getDomainOfValidity() domain of validity}
+     * and the {@linkplain CoordinateOperationContext#getAreaOfInterest() area of interest} is returned.
+     *
+     * <p>The default implementation performs the following steps:</p>
+     * <ul>
+     *   <li>Invoke {@link #createOperationFinder(CoordinateOperationAuthorityFactory, CoordinateOperationContext)}.</li>
+     *   <li>Invoke {@link CoordinateOperationFinder#createOperations(CoordinateReferenceSystem, CoordinateReferenceSystem)}
+     *       on the object returned by the previous step.</li>
+     * </ul>
+     *
+     * Subclasses can override {@link #createOperationFinder createOperationFinder(…)} if they need more control on
+     * the way coordinate operations are inferred.
+     *
+     * @param  sourceCRS  input coordinate reference system.
+     * @param  targetCRS  output coordinate reference system.
+     * @param  context    area of interest and desired accuracy, or {@code null}.
+     * @return coordinate operations from {@code sourceCRS} to {@code targetCRS}.
+     * @throws OperationNotFoundException if no operation path was found from {@code sourceCRS} to {@code targetCRS}.
+     * @throws FactoryException if the operation creation failed for some other reason.
+     *
+     * @see CoordinateOperationFinder
+     *
+     * @since 1.0
+     */
+    public List<CoordinateOperation> createOperations(final CoordinateReferenceSystem sourceCRS,
+                                                      final CoordinateReferenceSystem targetCRS,
+                                                      final CoordinateOperationContext context)
+            throws OperationNotFoundException, FactoryException
+    {
+        final AuthorityFactory registry = USE_EPSG_FACTORY ? CRS.getAuthorityFactory(Constants.EPSG) : null;
+        return createOperationFinder((registry instanceof CoordinateOperationAuthorityFactory) ?
+                (CoordinateOperationAuthorityFactory) registry : null, context).createOperations(sourceCRS, targetCRS);
+    }
+
+    /**
      * Creates the object which will perform the actual task of finding a coordinate operation path between two CRS.
      * This method is invoked by {@link #createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem,
      * CoordinateOperationContext) createOperation(…)} when no operation was found in the cache.

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java?rev=1828114&r1=1828113&r2=1828114&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java [UTF-8] Sat Mar 31 15:52:49 2018
@@ -91,7 +91,7 @@
  * for example by specifying the area of interest.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.6
  * @module
  */

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/package-info.java?rev=1828114&r1=1828113&r2=1828114&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/package-info.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/package-info.java [UTF-8] Sat Mar 31 15:52:49 2018
@@ -94,7 +94,7 @@
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Guilhem Legal (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.4
  * @module
  */

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java?rev=1828114&r1=1828113&r2=1828114&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java [UTF-8] Sat Mar 31 15:52:49 2018
@@ -66,7 +66,7 @@ import static org.junit.Assume.assumeTru
  * The operations are tested with various axis order and dimension in source and target CRS.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.7
  * @module
  */
@@ -150,6 +150,18 @@ public final strictfp class CoordinateOp
     }
 
     /**
+     * Gets exactly one coordinate operation from the registry to test.
+     */
+    private CoordinateOperation createOperation(final CoordinateReferenceSystem sourceCRS,
+                                                final CoordinateReferenceSystem targetCRS) throws FactoryException
+    {
+        registry.stopAtFirst = true;
+        final List<CoordinateOperation> operations = registry.createOperations(sourceCRS, targetCRS);
+        assertEquals("Invalid number of operations.", 1, operations.size());
+        return operations.get(0);
+    }
+
+    /**
      * Tests <cite>"NTF (Paris) to WGS 84 (1)"</cite> operation with source and target CRS conform to EPSG definitions.
      *
      * @throws ParseException if a CRS used in this test can not be parsed.
@@ -169,7 +181,7 @@ public final strictfp class CoordinateOp
                 // Intentionally omit Id[“EPSG”, 4807] for testing capability to find it back.
 
         final CoordinateReferenceSystem targetCRS = CommonCRS.WGS84.geographic();
-        final CoordinateOperation operation = registry.createOperation(sourceCRS, targetCRS);
+        final CoordinateOperation operation = createOperation(sourceCRS, targetCRS);
         verifyNTF(operation, "geog2D domain", true);
         /*
          * Same test point than the one used in FranceGeocentricInterpolationTest:
@@ -207,7 +219,7 @@ public final strictfp class CoordinateOp
                 "    Unit[“degree”, 0.017453292519943295]]");
 
         final CoordinateReferenceSystem targetCRS = CommonCRS.WGS84.normalizedGeographic();
-        final CoordinateOperation operation = registry.createOperation(sourceCRS, targetCRS);
+        final CoordinateOperation operation = createOperation(sourceCRS, targetCRS);
         verifyNTF(operation, "geog2D domain", false);
 
         transform  = operation.getMathTransform();
@@ -238,7 +250,7 @@ public final strictfp class CoordinateOp
                 "    Unit[“grad”, 0.015707963267948967]]");
 
         final CoordinateReferenceSystem sourceCRS = CommonCRS.WGS84.normalizedGeographic();
-        final CoordinateOperation operation = registry.createOperation(sourceCRS, targetCRS);
+        final CoordinateOperation operation = createOperation(sourceCRS, targetCRS);
 
         transform  = operation.getMathTransform();
         tolerance  = Formulas.ANGULAR_TOLERANCE;
@@ -270,7 +282,7 @@ public final strictfp class CoordinateOp
                 "    Axis[“Height (h)”, UP, Unit[“m”, 1]]]");
 
         final CoordinateReferenceSystem targetCRS = CommonCRS.WGS84.geographic3D();
-        final CoordinateOperation operation = registry.createOperation(sourceCRS, targetCRS);
+        final CoordinateOperation operation = createOperation(sourceCRS, targetCRS);
         verifyNTF(operation, "geog3D domain", false);
 
         transform  = operation.getMathTransform();
@@ -306,7 +318,7 @@ public final strictfp class CoordinateOp
 
         final CoordinateReferenceSystem targetCRS =
                 DefaultGeographicCRS.castOrCopy(CommonCRS.WGS84.geographic3D()).forConvention(AxesConvention.NORMALIZED);
-        final CoordinateOperation operation = registry.createOperation(sourceCRS, targetCRS);
+        final CoordinateOperation operation = createOperation(sourceCRS, targetCRS);
         verifyNTF(operation, "geog3D domain", false);
 
         transform  = operation.getMathTransform();
@@ -386,14 +398,14 @@ public final strictfp class CoordinateOp
     public void testFindDespiteDifferentAxisOrder() throws FactoryException {
         CoordinateReferenceSystem sourceCRS = crsFactory.createGeographicCRS("EPSG:4625");
         CoordinateReferenceSystem targetCRS = crsFactory.createGeographicCRS("EPSG:5489");
-        CoordinateOperation operation = registry.createOperation(sourceCRS, targetCRS);
+        CoordinateOperation operation = createOperation(sourceCRS, targetCRS);
         assertEpsgNameAndIdentifierEqual("Martinique 1938 to RGAF09 (1)", 5491, operation);
         /*
          * Above was only a verification using the source and target CRS expected by EPSG dataset.
          * Now the interesting test: use a target CRS with different axis order.
          */
         targetCRS = crsFactory.createGeographicCRS("EPSG:7086");
-        operation = registry.createOperation(sourceCRS, targetCRS);
+        operation = createOperation(sourceCRS, targetCRS);
         assertEpsgNameWithoutIdentifierEqual("Martinique 1938 to RGAF09 (1)", operation);
         final ParameterValueGroup p = ((SingleOperation) operation).getParameterValues();
         /*



Mime
View raw message