All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.referencing.operation.DefaultConcatenatedOperation Maven / Gradle / Ivy

Go to download

Implementations of Coordinate Reference Systems (CRS), conversion and transformation services derived from ISO 19111.

There is a newer version: 3.20-geoapi-3.0
Show newest version
/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2004-2011, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2011, Geomatys
 *
 *    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.geotoolkit.referencing.operation;

import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import net.jcip.annotations.Immutable;

import org.opengis.util.FactoryException;
import org.opengis.metadata.quality.PositionalAccuracy;
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.geotoolkit.referencing.operation.transform.ConcatenatedTransform;
import org.geotoolkit.util.collection.UnmodifiableArrayList;
import org.geotoolkit.util.converter.Classes;
import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.util.Utilities;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.io.wkt.Formatter;

import static org.geotoolkit.util.Utilities.hash;
import static org.geotoolkit.util.Utilities.deepEquals;
import static org.geotoolkit.util.ArgumentChecks.ensureNonNull;
import static org.geotoolkit.util.collection.XCollections.isNullOrEmpty;


/**
 * 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
 * (n+1) must be the same as the target coordinate reference system of step
 * (n). 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.
 *
 * @author Martin Desruisseaux (IRD, Geomatys)
 * @version 3.18
 *
 * @since 2.0
 * @module
 */
@Immutable
public class DefaultConcatenatedOperation extends AbstractCoordinateOperation
        implements ConcatenatedOperation
{
    /**
     * Serial number for inter-operability with different versions.
     */
    private static final long serialVersionUID = 4199619838029045700L;

    /**
     * The sequence of operations.
     */
    private final List 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
     * {@linkplain AbstractCoordinateOperation#AbstractCoordinateOperation(Map,
     * CoordinateReferenceSystem, CoordinateReferenceSystem, MathTransform) super-class
     * constructor}.
     *
     * @param properties Set of properties. Should contains at least {@code "name"}.
     * @param operations The sequence of operations.
     */
    public DefaultConcatenatedOperation(final Map properties,
                                        final CoordinateOperation... operations)
    {
        this(properties, new ArrayList(operations.length), 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
     * {@linkplain AbstractCoordinateOperation#AbstractCoordinateOperation(Map,
     * CoordinateReferenceSystem, CoordinateReferenceSystem, MathTransform) super-class
     * 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 properties,
                                        final CoordinateOperation[] operations,
                                        final MathTransformFactory factory)
            throws FactoryException
    {
        this(properties, new ArrayList(operations.length), 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 properties,
                                         final ArrayList 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 properties,
                                         final ArrayList 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 properties,
                                         final MathTransform transform,
                                         final List 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 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 target,
                                        final MathTransformFactory factory,
                                        final boolean wantTransform)
            throws FactoryException
    {
        MathTransform transform = null;
        ensureNonNull("operations", operations);
        for (int i=0; i cops = cop.getOperations();
                expand(cops.toArray(new CoordinateOperation[cops.size()]), target, factory, false);
            } else {
                throw new IllegalArgumentException(Errors.format(Errors.Keys.ILLEGAL_CLASS_$2,
                        Classes.getClass(op), SingleOperation.class));
            }
            /*
             * Checks 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(
                                Errors.Keys.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(
                        Errors.Keys.MISSING_PARAMETER_$1, "operations[" + size + ']'));
            }
        }
        return transform;
    }

    /**
     * If no accuracy were specified in the given properties map, adds 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. However 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.
     * 

* There is also a technical reasons for ignoring conversions. If a concatenated operation * contains a datum shift (i.e. a transformation) with unknown 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. *

* 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 mergeAccuracy(final Map properties, final List operations) { if (!properties.containsKey(COORDINATE_OPERATION_ACCURACY_KEY)) { Set accuracy = null; for (final CoordinateOperation op : operations) { if (op instanceof Transformation) { // See javadoc for a rational why we take only transformations in account. Collection candidates = op.getCoordinateOperationAccuracy(); if (!isNullOrEmpty(candidates)) { if (accuracy == null) { accuracy = new LinkedHashSet(); } accuracy.addAll(candidates); } } } if (accuracy != null) { final Map merged = new HashMap(properties); merged.put(COORDINATE_OPERATION_ACCURACY_KEY, accuracy.toArray(new PositionalAccuracy[accuracy.size()])); return merged; } } return properties; } /** * Returns the sequence of operations. */ @Override public List getOperations() { return operations; } /** * Compares this concatenated operation with the specified object for equality. * If the {@code mode} argument value is {@link ComparisonMode#STRICT STRICT} or * {@link ComparisonMode#BY_CONTRACT BY_CONTRACT}, then all available properties are * compared including the {@linkplain #getDomainOfValidity() domain of validity} and * the {@linkplain #getScope() scope}. * * @param object The object to compare to {@code this}. * @param mode {@link ComparisonMode#STRICT STRICT} for performing a strict comparison, or * {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} for comparing only properties * relevant to transformations. * @return {@code true} if both objects are equal. */ @Override public boolean equals(final Object object, final ComparisonMode mode) { if (object == this) { return true; // Slight optimization. } if (super.equals(object, mode)) { switch (mode) { case STRICT: { final DefaultConcatenatedOperation that = (DefaultConcatenatedOperation) object; return Utilities.equals(this.operations, that.operations); } default: { final ConcatenatedOperation that = (ConcatenatedOperation) object; return deepEquals(getOperations(), that.getOperations(), mode); } } } return false; } /** * {@inheritDoc} */ @Override protected int computeHashCode() { return hash(operations, super.computeHashCode()); } /** * {@inheritDoc} */ @Override public String formatWKT(final Formatter formatter) { final String label = super.formatWKT(formatter); for (final CoordinateOperation op : operations) { formatter.append(op); } return label; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy