Package org.geotools.referencing.operation

Source Code of org.geotools.referencing.operation.DefaultConcatenatedOperation

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2004-2008, Open Source Geospatial Foundation (OSGeo)
*
*    This library is free software; you can redistribute it and/or
*    modify it under the terms of the GNU Lesser General Public
*    License as published by the Free Software Foundation;
*    version 2.1 of the License.
*
*    This library is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*    Lesser General Public License for more details.
*
*    This package contains documentation from OpenGIS specifications.
*    OpenGIS consortium's work is fully acknowledged here.
*/
package org.geotools.referencing.operation;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.opengis.metadata.quality.PositionalAccuracy;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.SingleOperation;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.ConcatenatedOperation;
import org.opengis.referencing.operation.Transformation;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;

import org.geotools.referencing.wkt.Formatter;
import org.geotools.referencing.AbstractIdentifiedObject;
import org.geotools.referencing.operation.transform.ConcatenatedTransform;
import org.geotools.resources.UnmodifiableArrayList;
import org.geotools.resources.Classes;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;


/**
* An ordered sequence of two or more single coordinate operations. The sequence of operations is
* constrained by the requirement that the source coordinate reference system of step
* (<var>n</var>+1) must be the same as the target coordinate reference system of step
* (<var>n</var>). The source coordinate reference system of the first step and the target
* coordinate reference system of the last step are the source and target coordinate reference
* system associated with the concatenated operation.
*
* @since 2.1
*
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class DefaultConcatenatedOperation extends AbstractCoordinateOperation
        implements ConcatenatedOperation
{
    /**
     * Serial number for interoperability with different versions.
     */
    private static final long serialVersionUID = 4199619838029045700L;

    /**
     * The sequence of operations.
     */
    private final List<SingleOperation> operations;

    /**
     * Constructs a concatenated operation from the specified name.
     *
     * @param name The operation name.
     * @param operations The sequence of operations.
     */
    public DefaultConcatenatedOperation(final String name,
                                        final CoordinateOperation[] operations)
    {
        this(Collections.singletonMap(NAME_KEY, name), operations);
    }

    /**
     * Constructs a concatenated operation from a set of properties.
     * The properties given in argument follow the same rules than for the
     * {@link AbstractCoordinateOperation} constructor.
     *
     * @param properties Set of properties. Should contains at least {@code "name"}.
     * @param operations The sequence of operations.
     */
    public DefaultConcatenatedOperation(final Map<String,?> properties,
                                        final CoordinateOperation[] operations)
    {
        this(properties, new ArrayList<SingleOperation>(operations!=null ? operations.length : 4),
             operations);
    }

    /**
     * Constructs a concatenated operation from a set of properties and a
     * {@linkplain MathTransformFactory math transform factory}.
     * The properties given in argument follow the same rules than for the
     * {@link AbstractCoordinateOperation} constructor.
     *
     * @param  properties Set of properties. Should contains at least {@code "name"}.
     * @param  operations The sequence of operations.
     * @param  factory    The math transform factory to use for math transforms concatenation.
     * @throws FactoryException if the factory can't concatenate the math transforms.
     */
    public DefaultConcatenatedOperation(final Map<String,?> properties,
                                        final CoordinateOperation[] operations,
                                        final MathTransformFactory factory)
            throws FactoryException
    {
        this(properties, new ArrayList<SingleOperation>(operations!=null ? operations.length : 4),
             operations, factory);
    }

    /**
     * Work around for RFE #4093999 in Sun's bug database
     * ("Relax constraint on placement of this()/super() call in constructors").
     */
    private DefaultConcatenatedOperation(final Map<String,?> properties,
                                         final ArrayList<SingleOperation> list,
                                         final CoordinateOperation[] operations)
    {
        this(properties, expand(operations, list), list);
    }

    /**
     * Work around for RFE #4093999 in Sun's bug database
     * ("Relax constraint on placement of this()/super() call in constructors").
     */
    private DefaultConcatenatedOperation(final Map<String,?> properties,
                                         final ArrayList<SingleOperation> list,
                                         final CoordinateOperation[] operations,
                                         final MathTransformFactory factory)
            throws FactoryException
    {
        this(properties, expand(operations, list, factory, true), list);
    }

    /**
     * Work around for RFE #4093999 in Sun's bug database
     * ("Relax constraint on placement of this()/super() call in constructors").
     */
    private DefaultConcatenatedOperation(final Map<String,?> properties,
                                         final MathTransform transform,
                                         final List<SingleOperation> operations)
    {
        super(mergeAccuracy(properties, operations),
              operations.get(0).getSourceCRS(),
              operations.get(operations.size() - 1).getTargetCRS(),
              transform);
        this.operations = UnmodifiableArrayList.wrap(
                operations.toArray(new SingleOperation[operations.size()]));
    }

    /**
     * Work around for RFE #4093999 in Sun's bug database
     * ("Relax constraint on placement of this()/super() call in constructors").
     */
    private static MathTransform expand(final CoordinateOperation[] operations,
                                        final List<SingleOperation> list)
    {
        try {
            return expand(operations, list, null, true);
        } catch (FactoryException exception) {
            // Should not happen, since we didn't used any MathTransformFactory.
            throw new AssertionError(exception);
        }
    }

    /**
     * Transforms the list of operations into a list of single operations. This method
     * also check against null value and make sure that all CRS dimension matches.
     *
     * @param  operations The array of operations to expand.
     * @param  target The destination list in which to add {@code SingleOperation}.
     * @param  factory The math transform factory to use, or {@code null}
     * @param  wantTransform {@code true} if the concatenated math transform should be computed.
     *         This is set to {@code false} only when this method invokes itself recursively.
     * @return The concatenated math transform.
     * @throws FactoryException if the factory can't concatenate the math transforms.
     */
    private static MathTransform expand(final CoordinateOperation[] operations,
                                        final List<SingleOperation> target,
                                        final MathTransformFactory factory,
                                        final boolean wantTransform)
            throws FactoryException
    {
        MathTransform transform = null;
        ensureNonNull("operations", operations);
        for (int i=0; i<operations.length; i++) {
            ensureNonNull("operations", operations, i);
            final CoordinateOperation op = operations[i];
            if (op instanceof SingleOperation) {
                target.add((SingleOperation) op);
            } else if (op instanceof ConcatenatedOperation) {
                final ConcatenatedOperation cop = (ConcatenatedOperation) op;
                final List<SingleOperation> cops = cop.getOperations();
                expand(cops.toArray(new CoordinateOperation[cops.size()]), target, factory, false);
            } else {
                throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_CLASS_$2,
                        Classes.getClass(op), SingleOperation.class));
            }
            /*
             * Check the CRS dimensions.
             */
            if (i != 0) {
                final CoordinateReferenceSystem previous = operations[i-1].getTargetCRS();
                final CoordinateReferenceSystem next     = op             .getSourceCRS();
                if (previous!=null && next!=null) {
                    final int dim1 = previous.getCoordinateSystem().getDimension();
                    final int dim2 =     next.getCoordinateSystem().getDimension();
                    if (dim1 != dim2) {
                        throw new IllegalArgumentException(Errors.format(
                                  ErrorKeys.MISMATCHED_DIMENSION_$2, dim1, dim2));
                    }
                }
            }
            /*
             * Concatenates the math transform.
             */
            if (wantTransform) {
                final MathTransform step = op.getMathTransform();
                if (transform == null) {
                    transform = step;
                } else if (factory != null) {
                    transform = factory.createConcatenatedTransform(transform, step);
                } else {
                    transform = ConcatenatedTransform.create(transform, step);
                }
            }
        }
        if (wantTransform) {
            final int size = target.size();
            if (size <= 1) {
                throw new IllegalArgumentException(Errors.format(
                            ErrorKeys.MISSING_PARAMETER_$1, "operations[" + size + ']'));
            }
        }
        return transform;
    }

    /**
     * If no accuracy were specified in the given properties map, add all accuracies found in the
     * operation to concatenate. This method considers only {@link Transformation} components and
     * ignores all conversions. According ISO 19111, the accuracy attribute is allowed only for
     * transformations. However, this restriction is not enforced everywhere. The EPSG database
     * declares an accuracy of 0 meters for conversions, which is conceptually exact. Ourself we
     * are departing from the specification, since we are adding accuracy informations to a
     * concatenated operation. This departure should be considered as a convenience feature
     * only; accuracies are really relevant in transformations only.
     * <p>
     * There is also a technical reasons for ignoring conversions. If a concatenated operation
     * contains a datum shift (i.e. a transformation) with unknow accuracy, and a projection
     * (i.e. a conversion) with a declared 0 meter error, we don't want to declare this 0 meter
     * error as the concatenated operation's accuracy; it would be a false information.
     * <p>
     * Note that a concatenated operation typically contains an arbitrary amount of conversions,
     * but only one transformation. So considering transformation only usually means to pickup
     * only one operation in the given {@code operations} list.
     *
     * @todo We should use a Map and merge only one accuracy for each specification.
     */
    private static Map<String,?> mergeAccuracy(final Map<String,?> properties,
            final List<? extends CoordinateOperation> operations)
    {
        if (!properties.containsKey(COORDINATE_OPERATION_ACCURACY_KEY)) {
            Set<PositionalAccuracy> accuracy = null;
            for (final CoordinateOperation op : operations) {
                if (op instanceof Transformation) {
                    // See javadoc for a rational why we take only transformations in account.
                    Collection<PositionalAccuracy> candidates = op.getCoordinateOperationAccuracy();
                    if (candidates!=null && !candidates.isEmpty()) {
                        if (accuracy == null) {
                            accuracy = new LinkedHashSet<PositionalAccuracy>();
                        }
                        accuracy.addAll(candidates);
                    }
                }
            }
            if (accuracy != null) {
                final Map<String,Object> merged = new HashMap<String,Object>(properties);
                merged.put(COORDINATE_OPERATION_ACCURACY_KEY,
                           accuracy.toArray(new PositionalAccuracy[accuracy.size()]));
                return merged;
            }
        }
        return properties;
    }

    /**
     * Returns the sequence of operations.
     */
    public List<SingleOperation> getOperations() {
        return operations;
    }

    /**
     * Compare this concatenated operation with the specified object for equality.
     * If {@code compareMetadata} is {@code true}, then all available properties are
     * compared including {@linkplain #getValidArea valid area} and {@linkplain #getScope scope}.
     *
     * @param  object The object to compare to {@code this}.
     * @param  compareMetadata {@code true} for performing a strict comparaison, or
     *         {@code false} for comparing only properties relevant to transformations.
     * @return {@code true} if both objects are equal.
     */
    @Override
    public boolean equals(final AbstractIdentifiedObject object, final boolean compareMetadata) {
        if (object == this) {
            return true; // Slight optimization.
        }
        if (super.equals(object, compareMetadata)) {
            final DefaultConcatenatedOperation that = (DefaultConcatenatedOperation) object;
            return equals(this.operations, that.operations, compareMetadata);
        }
        return false;
    }

    /**
     * Returns a hash code value for this concatenated operation.
     */
    @Override
    public int hashCode() {
        return operations.hashCode() ^ (int)serialVersionUID;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected String formatWKT(final Formatter formatter) {
        final String label = super.formatWKT(formatter);
        for (final Iterator it=operations.iterator(); it.hasNext();) {
            formatter.append((CoordinateOperation) it.next());
        }
        return label;
    }
}
TOP

Related Classes of org.geotools.referencing.operation.DefaultConcatenatedOperation

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.