Added: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java?rev=1738311&view=auto
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
 (added)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
 [UTF-8] Fri Apr  8 21:48:58 2016
@@ -0,0 +1,572 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import javax.measure.converter.ConversionException;
+
+import org.opengis.util.FactoryException;
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.NoSuchAuthorityCodeException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.GeodeticCRS;
+import org.opengis.referencing.cs.EllipsoidalCS;
+import org.opengis.referencing.operation.*;
+
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.cs.CoordinateSystems;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
+import org.apache.sis.metadata.iso.extent.Extents;
+import org.apache.sis.internal.metadata.ReferencingServices;
+import org.apache.sis.internal.system.Loggers;
+import org.apache.sis.internal.util.Citations;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Utilities;
+import org.apache.sis.util.Classes;
+import org.apache.sis.util.Deprecable;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.collection.Containers;
+import org.apache.sis.util.collection.BackingStoreException;
+
+
+/**
+ * Searches coordinate operations in a registry maintained by an authority 
(typically EPSG).
+ * Authority factory may help to find transformation paths not available 
otherwise
+ * (often determined from empirical parameters).
+ * Authority factories can also provide additional informations like the
+ * {@linkplain AbstractCoordinateOperation#getDomainOfValidity() domain of 
validity},
+ * {@linkplain AbstractCoordinateOperation#getScope() scope} and
+ * {@linkplain AbstractCoordinateOperation#getCoordinateOperationAccuracy() 
accuracy}.
+ *
+ * <p>When <code>{@linkplain #createOperation createOperation}(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 
CoordinateOperationAuthorityFactory#createFromCoordinateReferenceSystemCodes
+ * createFromCoordinateReferenceSystemCodes}(sourceCode, targetCode)</code> 
method.
+ * If the authority factory does not know about the specified CRS,
+ * then {@link CoordinateOperationInference} is used as a fallback.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+final class CoordinateOperationRegistry extends CoordinateOperationFinder {
+    /**
+     * The registry authority. This is typically EPSG.
+     */
+    private final Citation authority;
+
+    /**
+     * The object to use for finding authority codes.
+     */
+    private final IdentifiedObjectFinder codeFinder;
+
+    /**
+     * The authority factory to use for creating new operations.
+     */
+    private final CoordinateOperationAuthorityFactory registry;
+
+    /**
+     * Creates a new finder for the given factory.
+     */
+    CoordinateOperationRegistry(final DefaultCoordinateOperationFactory   
factory,
+                                final CoordinateOperationAuthorityFactory 
registry,
+                                final CoordinateOperationContext          
context) throws FactoryException
+    {
+        super(factory, context);
+        this.registry  = registry;
+        authority  = registry.getAuthority();
+        codeFinder = 
IdentifiedObjects.newFinder(Citations.getIdentifier(authority, false));
+        codeFinder.setSearchDomain(IdentifiedObjectFinder.Domain.ALL_DATASET);
+        codeFinder.setIgnoringAxes(true);
+    }
+
+    /**
+     * Finds the authority code for the given coordinate reference system.
+     * This method does not trust the code given by the user in its CRS - we 
verify it.
+     */
+    private String findCode(final CoordinateReferenceSystem crs) throws 
FactoryException {
+        final Identifier identifier = 
IdentifiedObjects.getIdentifier(codeFinder.findSingleton(crs), authority);
+        return (identifier != null) ? identifier.getCode() : null;
+    }
+
+    /**
+     * Returns an operation for conversion or transformation 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}.
+     *
+     * @param  sourceCRS  source coordinate reference system.
+     * @param  targetCRS  target 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.
+     * @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 ConversionException 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)
+            throws IllegalArgumentException, ConversionException, 
FactoryException
+    {
+        final String sourceID = findCode(sourceCRS);
+        if (sourceID == null) {
+            return null;
+        }
+        final String targetID = findCode(targetCRS);
+        if (targetID == null) {
+            return null;
+        }
+        if (sourceID.equals(targetID)) {
+            /*
+             * Above check is necessary because this method may be invoked in 
some situations where the code
+             * are equal while the CRS are not. Such situation should be 
illegal, but unfortunately it still
+             * happen because many softwares are not compliant with EPSG 
definition of axis order.   In such
+             * cases we will need to compute a transform from sourceCRS to 
targetCRS ignoring the source and
+             * target codes.   The CoordinateOperationInference class can do 
that, providing that we prevent
+             * this CoordinateOperationRegistry to (legitimately) claims that 
the operation from sourceCode
+             * to targetCode is the identity transform.
+             */
+            return null;
+        }
+        final boolean inverse;
+        Set<CoordinateOperation> operations;
+        try {
+            operations = 
registry.createFromCoordinateReferenceSystemCodes(sourceID, targetID);
+            inverse = Containers.isNullOrEmpty(operations);
+            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 (operations == null) {
+                    return null;
+                }
+            }
+        } catch (NoSuchAuthorityCodeException exception) {
+            /*
+             * sourceCode or targetCode is unknown to the underlying authority 
factory.
+             * Ignores the exception and fallback on the generic algorithm 
provided by
+             * CoordinateOperationInference.
+             */
+            log(exception, false);
+            return null;
+        }
+        /*
+         * 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).
+         */
+        double largestArea = 0;
+        double finestAccuracy = Double.POSITIVE_INFINITY;
+        CoordinateOperation bestChoice = null;
+        boolean stopAtFirstDeprecated = false;
+        for (final Iterator<CoordinateOperation> it=operations.iterator(); 
it.hasNext();) {
+            CoordinateOperation candidate;
+            try {
+                candidate = it.next();
+            } catch (BackingStoreException exception) {
+                throw exception.unwrapOrRethrow(FactoryException.class);
+            }
+            if (inverse) try {
+                candidate = inverse(candidate);
+            } catch (NoninvertibleTransformException exception) {
+                // It may be a normal failure - the operation is not required 
to be invertible.
+                
Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
+                        CoordinateOperationRegistry.class, "search", 
exception);
+                continue;
+            }
+            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).
+                 */
+                final boolean isDeprecated = (candidate instanceof Deprecable) 
&& ((Deprecable) candidate).isDeprecated();
+                if (isDeprecated && stopAtFirstDeprecated) {
+                    break;
+                }
+                final double area = Extents.area(Extents.intersection(bbox, 
Extents.getGeographicBoundingBox(candidate.getDomainOfValidity())));
+                if (bestChoice == null || area >= largestArea) {
+                    final double accuracy = CRS.getLinearAccuracy(candidate);
+                    if (bestChoice == null || area != largestArea || accuracy 
< finestAccuracy) {
+                        /*
+                         * It is possible that the CRS given to this method 
were not quite right.  For example the user
+                         * may have created his CRS from a WKT using a 
different axis order than the order specified by
+                         * the authority and still (wrongly) call those CRS 
"EPSG:xxxx".  So we check if the source and
+                         * target CRS for the operation we just created are 
equivalent to the CRS specified by the user.
+                         *
+                         * NOTE: FactoryException may be thrown if we failed 
to create a transform from the user-provided
+                         * CRS to the authority-provided CRS. That transform 
should have been only an identity transform,
+                         * or a simple affine transform if the user specified 
wrong CRS as explained in above paragraph.
+                         * If we failed here, we are likely to fail for all 
other transforms. So we are better to let
+                         * the FactoryException propagate.
+                         */
+                        candidate = complete(candidate, sourceCRS, targetCRS);
+                        bestChoice = candidate;
+                        if (!Double.isNaN(area)) {
+                            largestArea = area;
+                        }
+                        finestAccuracy = Double.isNaN(accuracy) ? 
Double.POSITIVE_INFINITY : accuracy;
+                        stopAtFirstDeprecated = !isDeprecated;
+                    }
+                }
+            }
+        }
+        return bestChoice;
+    }
+
+    /**
+     * Returns the inverse of the specified operation.
+     *
+     * @param  operation The operation to invert, or {@code null}.
+     * @return The inverse of {@code operation}, or {@code null} if none.
+     * @throws NoninvertibleTransformException if the operation is not 
invertible.
+     * @throws FactoryException if the operation creation failed for an other 
reason.
+     */
+    protected CoordinateOperation inverse(final CoordinateOperation operation)
+            throws NoninvertibleTransformException, FactoryException
+    {
+        if (operation instanceof SingleOperation) {
+            return super.inverse((SingleOperation) operation);
+        }
+        if (operation instanceof ConcatenatedOperation) {
+            final List<? extends CoordinateOperation> operations = 
((ConcatenatedOperation) operation).getOperations();
+            final CoordinateOperation[] inverted = new 
CoordinateOperation[operations.size()];
+            for (int i=0; i<inverted.length;) {
+                final CoordinateOperation op = inverse(operations.get(i));
+                if (op == null) {
+                    return null;
+                }
+                inverted[inverted.length - ++i] = op;
+            }
+            return 
factory.createConcatenatedOperation(properties(INVERSE_OPERATION), inverted);
+        }
+        return null;
+    }
+
+    /**
+     * Completes (if necessary) the given coordinate operation for making sure 
that the source CRS
+     * is the given one and the target CRS is the given one.  In principle, 
the given CRS shall be
+     * equivalent to the operation source/target CRS. However discrepancies 
happen if the user CRS
+     * have flipped axis order, or if we looked for 2D operation while the 
user provided 3D CRS.
+     *
+     * @param  operation  the coordinate operation to complete.
+     * @param  sourceCRS  the source CRS requested by the user.
+     * @param  targetCRS  the target CRS requested by the user.
+     * @return a coordinate operation for the given source and target CRS.
+     * @throws IllegalArgumentException if the coordinate systems are not of 
the same type or axes do not match.
+     * @throws ConversionException if the units are not compatible or a unit 
conversion is non-linear.
+     * @throws FactoryException if the operation can not be constructed.
+     */
+    private CoordinateOperation complete(final CoordinateOperation       
operation,
+                                         final CoordinateReferenceSystem 
sourceCRS,
+                                         final CoordinateReferenceSystem 
targetCRS)
+            throws IllegalArgumentException, ConversionException, 
FactoryException
+    {
+        CoordinateReferenceSystem source = operation.getSourceCRS();
+        CoordinateReferenceSystem target = operation.getTargetCRS();
+        final MathTransformFactory mtFactory = 
factorySIS.getMathTransformFactory();
+        final MathTransform prepend = swapAndScaleAxes(sourceCRS, source, 
mtFactory);
+        final MathTransform append  = swapAndScaleAxes(target, targetCRS, 
mtFactory);
+        if (prepend != null) source = sourceCRS;
+        if (append  != null) target = targetCRS;
+        return transform(source, prepend, operation, append, target, 
mtFactory);
+    }
+
+    /**
+     * Returns an affine transform between two coordinate systems.
+     * Only units and axes order are taken in account by this method.
+     *
+     * @param  sourceCRS  the source coordinate reference system.
+     * @param  targetCRS  the target coordinate reference system.
+     * @param  mtFactory  the math transform factory to use.
+     * @return the transform from the given source to the given target CRS, or 
{@code null} if none is needed.
+     * @throws IllegalArgumentException if the coordinate systems are not of 
the same type or axes do not match.
+     * @throws ConversionException if the units are not compatible or a unit 
conversion is non-linear.
+     * @throws FactoryException if an error occurred while creating a math 
transform.
+     */
+    private static MathTransform swapAndScaleAxes(final 
CoordinateReferenceSystem sourceCRS,
+                                                  final 
CoordinateReferenceSystem targetCRS,
+                                                  final MathTransformFactory   
   mtFactory)
+            throws IllegalArgumentException, ConversionException, 
FactoryException
+    {
+        assert Utilities.deepEquals(sourceCRS, targetCRS, 
ComparisonMode.ALLOW_VARIANT);
+        final Matrix m = 
CoordinateSystems.swapAndScaleAxes(sourceCRS.getCoordinateSystem(), 
targetCRS.getCoordinateSystem());
+        return (m.isIdentity()) ? null : mtFactory.createAffineTransform(m);
+    }
+
+    /**
+     * Appends or prepends the specified math transforms to the transform of 
the given operation.
+     * The new coordinate operation (if any) will share the same metadata than 
the original operation,
+     * except the authority code.
+     *
+     * <p>This method is used in order to change axis order when the 
user-specified CRS disagree
+     * with the authority-supplied CRS.</p>
+     *
+     * @param  sourceCRS  the source CRS to give to the new operation.
+     * @param  prepend    the transform to prepend to the operation math 
transform, or {@code null} if none.
+     * @param  operation  the operation in which to prepend the math 
transforms.
+     * @param  append     the transform to append to the operation math 
transform, or {@code null} if none.
+     * @param  targetCRS  the target CRS to give to the new operation.
+     * @param  mtFactory  the math transform factory to use.
+     * @return a new operation, or {@code operation} if {@code prepend} and 
{@code append} were nulls or identity transforms.
+     * @throws FactoryException if the operation can not be constructed.
+     */
+    private CoordinateOperation transform(final CoordinateReferenceSystem 
sourceCRS,
+                                          final MathTransform             
prepend,
+                                                CoordinateOperation       
operation,
+                                          final MathTransform             
append,
+                                          final CoordinateReferenceSystem 
targetCRS,
+                                          final MathTransformFactory      
mtFactory)
+            throws FactoryException
+    {
+        if ((prepend == null || prepend.isIdentity()) && (append == null || 
append.isIdentity())) {
+            return operation;
+        }
+        /*
+         * In the particular case of concatenated operations, we can not 
prepend or append a math transform to
+         * the operation as a whole (the math transform for a concatenated 
operation is computed automatically
+         * as the concatenation of the transforms from every single 
operations, and we need to stay consistent
+         * with that). Instead, prepend to the first single operation and 
append to the last single operation.
+         */
+        if (operation instanceof ConcatenatedOperation) {
+            final List<? extends CoordinateOperation> c = 
((ConcatenatedOperation) operation).getOperations();
+            final CoordinateOperation[] op = c.toArray(new 
CoordinateOperation[c.size()]);
+            switch (op.length) {
+                case 0: break;                              // Illegal, but we 
are paranoiac.
+                case 1: operation = op[0]; break;           // Useless 
ConcatenatedOperation.
+                default: {
+                    final int n = op.length - 1;
+                    final CoordinateOperation first = op[0];
+                    final CoordinateOperation last  = op[n];
+                    op[0] = transform(sourceCRS, prepend, first, null, 
first.getTargetCRS(), mtFactory);
+                    op[n] = transform(last.getSourceCRS(), null, last, append, 
targetCRS,    mtFactory);
+                    return 
factory.createConcatenatedOperation(derivedFrom(operation), op);
+                }
+            }
+        }
+        /*
+         * Single operation case.
+         */
+        MathTransform transform = operation.getMathTransform();
+        if (prepend != null) transform = 
mtFactory.createConcatenatedTransform(prepend, transform);
+        if (append  != null) transform = 
mtFactory.createConcatenatedTransform(transform, append);
+        assert !transform.equals(operation.getMathTransform()) : transform;
+        return recreate(operation, sourceCRS, targetCRS, transform);
+    }
+
+    /**
+     * Creates a new coordinate operation with the same method than the given 
operation, but different CRS.
+     * The CRS may differ either in the number of dimensions (i.e. let the 
vertical coordinate pass through),
+     * or in axis order (i.e. axis order in user CRS were not compliant with 
authority definition).
+     *
+     * @param  operation  the operation specified by the authority.
+     * @param  sourceCRS  the source CRS specified by the user.
+     * @param  targetCRS  the target CRS specified by the user
+     * @param  transform  the math transform to use in replacement to the one 
in {@code operation}.
+     * @return a new operation from the given source CRS to target CRS using 
the given transform.
+     * @throws FactoryException if an error occurred while creating the new 
operation.
+     */
+    private CoordinateOperation recreate(final CoordinateOperation       
operation,
+                                         final CoordinateReferenceSystem 
sourceCRS,
+                                         final CoordinateReferenceSystem 
targetCRS,
+                                         final MathTransform             
transform) throws FactoryException
+    {
+        Class<? extends CoordinateOperation> type;                  // GeoAPI 
type for the given operation.
+        if (operation instanceof AbstractCoordinateOperation) {
+            type = ((AbstractCoordinateOperation) operation).getInterface();
+        } else {
+            // Fallback (less reliable) for non-SIS implementations.
+            final Class<? extends CoordinateOperation>[] types =
+                    Classes.getLeafInterfaces(operation.getClass(), 
CoordinateOperation.class);
+            type = (types.length != 0) ? types[0] : SingleOperation.class;
+        }
+        OperationMethod method = null;
+        if (operation instanceof SingleOperation) {
+            method = DefaultOperationMethod.redimension(((SingleOperation) 
operation).getMethod(),
+                            transform.getSourceDimensions(), 
transform.getTargetDimensions());
+        }
+        final Map<String,Object> properties = new 
HashMap<>(derivedFrom(operation));
+        properties.put(ReferencingServices.OPERATION_TYPE_KEY, type);
+        return factorySIS.createSingleOperation(properties, sourceCRS, 
targetCRS,
+                AbstractCoordinateOperation.getInterpolationCRS(operation), 
method, transform);
+    }
+
+    /**
+     * Returns a new coordinate operation with the ellipsoidal height added 
either in the source coordinates,
+     * in the target coordinates or both. If there is an ellipsoidal 
transform, then this method updates the
+     * transforms in order to use the ellipsoidal height (it has an impact on 
the transformed values).
+     *
+     * <p>This method requires that the EPSG factory insert explicit 
<cite>"Geographic3D to 2D conversion"</cite>
+     * operations (EPSG:9659) in the operations chain, or an equivalent 
operation (recognized by its matrix shape).
+     * This method tries to locate and remove EPSG:9659 or equivalent 
operation from the operation chain in order
+     * to get three-dimensional domains.</p>
+     *
+     * <p>This method is not guaranteed to succeed in adding the ellipsoidal 
height. It works on a
+     * <cite>best effort</cite> basis. In any cases, the {@link #complete} 
method should be invoked
+     * after this one in order to ensure that the source and target CRS are 
the expected ones.</p>
+     *
+     * @param  operation the original (typically two-dimensional) coordinate 
operation.
+     * @param  source3D  {@code true} for adding ellipsoidal height in source 
coordinates.
+     * @param  target3D  {@code true} for adding ellipsoidal height in target 
coordinates.
+     * @return a coordinate operation with the source and/or target 
coordinates made 3D,
+     *         or {@code null} if this method does not know how to create the 
operation.
+     * @throws FactoryException if an error occurred while creating the 
coordinate operation.
+     */
+    private CoordinateOperation propagateVertical(ConcatenatedOperation 
operation,
+            final boolean source3D, final boolean target3D) throws 
FactoryException
+    {
+        final List<CoordinateOperation> operations = new 
ArrayList<>(operation.getOperations());
+        if ((source3D && !propagateVertical(operations.listIterator(), true)) 
||
+            (target3D && 
!propagateVertical(operations.listIterator(operations.size()), false)))
+        {
+            return null;
+        }
+        return factory.createConcatenatedOperation(derivedFrom(operation),
+                operations.toArray(new 
CoordinateOperation[operations.size()]));
+    }
+
+    /**
+     * Appends a vertical axis in the source CRS of the first step {@code 
forward = true} or in
+     * the target CRS of the last step {@code forward = false} of the given 
operations chain.
+     *
+     * @param  operations  the chain of operations in which to add a vertical 
axis.
+     * @param  forward     {@code true} for adding the vertical axis at the 
beginning, or
+     *                     {@code false} for adding the vertical axis at the 
end.
+     * @return {@code true} on success.
+     */
+    private boolean propagateVertical(final ListIterator<CoordinateOperation> 
operations, final boolean forward)
+            throws FactoryException
+    {
+        while (forward ? operations.hasNext() : operations.hasPrevious()) {
+            final CoordinateOperation op = forward ? operations.next() : 
operations.previous();
+            /*
+             * We will accept to increase the number of dimensions only for 
operations between geographic CRS.
+             * We do not increase the number of dimensions for operations 
between other kind of CRS because we
+             * would not know which value to give to the new dimension.
+             */
+            CoordinateReferenceSystem sourceCRS, targetCRS;
+            if (! ((sourceCRS = op.getSourceCRS()) instanceof GeodeticCRS
+                && (targetCRS = op.getTargetCRS()) instanceof GeodeticCRS
+                && sourceCRS.getCoordinateSystem() instanceof EllipsoidalCS
+                && targetCRS.getCoordinateSystem() instanceof EllipsoidalCS))
+            {
+                break;
+            }
+            /*
+             * We can process only linear operations, otherwise we would not 
know how to add a dimension.
+             * Examples of linear operations are:
+             *
+             *   - Longitude rotation (EPSG:9601). Note that this is a 
transformation rather than a conversion.
+             *   - Geographic3D to 2D conversion (EPSG:9659).
+             */
+            Matrix matrix = MathTransforms.getMatrix(op.getMathTransform());
+            if (matrix == null) {
+                break;
+            }
+            /*
+             * We can process only one of the following cases:
+             *
+             *   - Replace a 2D → 2D operation by a 3D → 3D one (i.e. add a 
passthrough operation).
+             *   - Usually remove (or otherwise edit) the operation that 
change the number of dimensions
+             *     between the 2D and 3D cases.
+             */
+            final int numRow = matrix.getNumRow();
+            final int numCol = matrix.getNumCol();
+            final boolean is2D     = (numCol == 3 && numRow == 3);         // 
2D → 2D operation.
+            if (!(is2D || (forward ? (numCol == 3 && numRow == 4)          // 
2D → 3D operation.
+                                   : (numCol == 4 && numRow == 3))))       // 
3D → 2D operation.
+            {
+                break;
+            }
+            matrix = Matrices.resizeAffine(matrix, 4, 4);
+            if (matrix.isIdentity()) {
+                operations.remove();
+            } else {
+                /*
+                 * If we can not just remove the operation, build a new one 
with the expected number of dimensions.
+                 * The new operation will inherit the same properties except 
the identifiers, since it is no longer
+                 * conform to the definition provided by the authority.
+                 */
+                final MathTransform mt = 
factorySIS.getMathTransformFactory().createAffineTransform(matrix);
+                operations.set(recreate(op, toGeodetic3D(sourceCRS), 
toGeodetic3D(targetCRS), mt));
+            }
+            /*
+             * If we processed the operation that change the number of 
dimensions, we are done.
+             */
+            if (!is2D) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * If the given CRS is two-dimensional, append an ellipsoidal height to it.
+     * It is caller's responsibility to ensure that the given CRS is 
geographic.
+     */
+    private CoordinateReferenceSystem toGeodetic3D(CoordinateReferenceSystem 
crs) throws FactoryException {
+        assert (crs instanceof GeodeticCRS) && (crs.getCoordinateSystem() 
instanceof EllipsoidalCS) : crs;
+        if (crs.getCoordinateSystem().getDimension() == 2) {
+            crs = 
ReferencingServices.getInstance().createCompoundCRS(factorySIS.getCRSFactory(), 
factorySIS.getCSFactory(),
+                    derivedFrom(crs), crs, 
CommonCRS.Vertical.ELLIPSOIDAL.crs());
+        }
+        return crs;
+    }
+
+    /**
+     * Returns the properties of the given object, excluding the identifiers.
+     * This is used for new objects derived from an object specified by the 
authority.
+     * Since the new object is not strictly as defined by the authority, we 
can not keep its identifier code.
+     */
+    private static Map<String,?> derivedFrom(final IdentifiedObject object) {
+        return IdentifiedObjects.getProperties(object, 
CoordinateOperation.IDENTIFIERS_KEY);
+    }
+
+    /**
+     * Logs an exception. This method pretends that the logging come from 
{@link DefaultCoordinateOperationFactory}
+     * since this is the public API which use this {@code 
CoordinateOperationRegistry} class.
+     *
+     * @param exception  the exception which occurred.
+     * @param trace      whether the include the stack trace.
+     */
+    private void log(final Exception exception, final boolean trace) {
+        final LogRecord record = new LogRecord(Level.WARNING, 
exception.getLocalizedMessage());
+        if (trace) {
+            record.setThrown(exception);
+        }
+        record.setLoggerName(Loggers.COORDINATE_OPERATION);
+        Logging.log(DefaultCoordinateOperationFactory.class, 
"createOperation", record);
+    }
+}

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

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

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java?rev=1738311&r1=1738310&r2=1738311&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
 [UTF-8] Fri Apr  8 21:48:58 2016
@@ -624,6 +624,9 @@ next:   for (int i=components.size(); --
     public CoordinateOperation createConcatenatedOperation(final Map<String,?> 
properties,
             final CoordinateOperation... operations) throws FactoryException
     {
+        if (operations != null && operations.length == 1) {
+            return operations[0];
+        }
         final CoordinateOperation op;
         try {
             op = new DefaultConcatenatedOperation(properties, operations, 
getMathTransformFactory());


Reply via email to