sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1738311 [2/2] - in /sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing: factory/ operation/
Date Fri, 08 Apr 2016 21:48:58 GMT
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());



Mime
View raw message