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

org.geotoolkit.referencing.operation.DefaultOperationMethod 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) 2001-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.HashMap;
import java.util.Collections;
import net.jcip.annotations.Immutable;

import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.Formula;
import org.opengis.referencing.operation.SingleOperation;
import org.opengis.referencing.operation.Projection;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.OperationMethod;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.parameter.InvalidParameterValueException;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.metadata.citation.Citation;

import org.geotoolkit.util.Utilities;
import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.parameter.Parameters;
import org.geotoolkit.referencing.IdentifiedObjects;
import org.geotoolkit.referencing.AbstractIdentifiedObject;
import org.geotoolkit.referencing.operation.transform.Parameterized;
import org.geotoolkit.referencing.operation.transform.LinearTransform;
import org.geotoolkit.referencing.operation.transform.ConcatenatedTransform;
import org.geotoolkit.referencing.operation.transform.PassThroughTransform;
import org.geotoolkit.resources.Vocabulary;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.io.wkt.Formatter;

import static org.geotoolkit.util.Utilities.hash;
import static org.geotoolkit.util.ArgumentChecks.*;


/**
 * Definition of an algorithm used to perform a coordinate operation. Most operation
 * methods use a number of operation parameters, although some coordinate conversions
 * use none. Each coordinate operation using the method assigns values to these parameters.
 *
 * @author Martin Desruisseaux (IRD, Geomatys)
 * @version 3.18
 *
 * @see DefaultSingleOperation
 *
 * @since 2.0
 * @module
 */
@Immutable
public class DefaultOperationMethod extends AbstractIdentifiedObject implements OperationMethod {
    /**
     * Serial number for inter-operability with different versions.
     */
    private static final long serialVersionUID = -8181774670648793964L;

    /**
     * List of localizable properties. To be given to {@link AbstractIdentifiedObject} constructor.
     */
    private static final String[] LOCALIZABLES = {FORMULA_KEY};

    /**
     * Formula(s) or procedure used by this operation method. This may be a reference to a
     * publication. Note that the operation method may not be analytic, in which case this
     * attribute references or contains the procedure, not an analytic formula.
     */
    private final Formula formula;

    /**
     * Number of dimensions in the source CRS of this operation method.
     * May be {@code null} if this method can work with any number of
     * source dimensions (e.g. Affine Transform).
     */
    protected final Integer sourceDimension;

    /**
     * Number of dimensions in the target CRS of this operation method.
     * May be {@code null} if this method can work with any number of
     * target dimensions (e.g. Affine Transform).
     */
    protected final Integer targetDimension;

    /**
     * The set of parameters, or {@code null} if none.
     */
    private final ParameterDescriptorGroup parameters;

    /**
     * Convenience constructor that creates an operation method from a math transform.
     * The information provided in the newly created object are approximative, and
     * usually acceptable only as a fallback when no other information are available.
     *
     * @param transform The math transform to describe.
     */
    public DefaultOperationMethod(final MathTransform transform) {
        this(getProperties(transform),
             transform.getSourceDimensions(),
             transform.getTargetDimensions(),
             getDescriptor(transform));
    }

    /**
     * Work around for RFE #4093999 in Sun's bug database
     * ("Relax constraint on placement of this()/super() call in constructors").
     */
    private static Map getProperties(final MathTransform transform) {
        ensureNonNull("transform", transform);
        if (transform instanceof Parameterized) {
            final Parameterized mt = (Parameterized) transform;
            final ParameterDescriptorGroup parameters = mt.getParameterDescriptors();
            if (parameters != null) {
                return IdentifiedObjects.getProperties(parameters, null);
            }
        }
        return Collections.singletonMap(NAME_KEY, Vocabulary.format(Vocabulary.Keys.UNKNOWN));
    }

    /**
     * Work around for RFE #4093999 in Sun's bug database
     * ("Relax constraint on placement of this()/super() call in constructors").
     * This code should have been merged with {@code getProperties} above.
     */
    private static ParameterDescriptorGroup getDescriptor(final MathTransform transform) {
        ParameterDescriptorGroup descriptor = null;
        if (transform instanceof Parameterized) {
            descriptor = ((Parameterized) transform).getParameterDescriptors();
        }
        return descriptor;
    }

    /**
     * Constructs a new operation method with the same values than the specified one.
     * This copy constructor provides a way to convert an arbitrary implementation into a
     * Geotk one or a user-defined one (as a subclass), usually in order to leverage
     * some implementation-specific API. This constructor performs a shallow copy,
     * i.e. the properties are not cloned.
     *
     * @param method The operation method to copy.
     */
    public DefaultOperationMethod(final OperationMethod method) {
        super(method);
        formula         = method.getFormula();
        parameters      = method.getParameters();
        sourceDimension = method.getSourceDimensions();
        targetDimension = method.getTargetDimensions();
    }

    /**
     * Constructs a new operation method with the same values than the specified one except the
     * dimensions. The source and target dimensions may be {@code null} if this method can work
     * with any number of dimensions (e.g. Affine Transform).
     *
     * @param method The operation method to copy.
     * @param sourceDimension Number of dimensions in the source CRS of this operation method.
     * @param targetDimension Number of dimensions in the target CRS of this operation method.
     */
    public DefaultOperationMethod(final OperationMethod method,
                                  final Integer sourceDimension,
                                  final Integer targetDimension)
    {
        super(method);
        this.formula    = method.getFormula();
        this.parameters = method.getParameters();
        this.sourceDimension = sourceDimension;
        this.targetDimension = targetDimension;
        checkDimension();
    }

    /**
     * Constructs an operation method from a set of properties and a descriptor group.
     * The properties given in argument follow the same rules than for the
     * {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}.
     * Additionally, the following properties are understood by this construtor:
     * 

*

* * * * * * * * * * *
Property nameValue typeValue given to
 {@value org.opengis.referencing.operation.OperationMethod#FORMULA_KEY}  {@link Formula}, {@link Citation} or {@link CharSequence}  {@link #getFormula}
*

* The source and target dimensions may be {@code null} if this method can work * with any number of dimensions (e.g. Affine Transform). * * @param properties Set of properties. Should contains at least {@code "name"}. * @param sourceDimension Number of dimensions in the source CRS of this operation method. * @param targetDimension Number of dimensions in the target CRS of this operation method. * @param parameters The set of parameters, or {@code null} if none. */ public DefaultOperationMethod(final Map properties, final Integer sourceDimension, final Integer targetDimension, final ParameterDescriptorGroup parameters) { this(properties, new HashMap(), sourceDimension, targetDimension, parameters); } /** * Work around for RFE #4093999 in Sun's bug database * ("Relax constraint on placement of this()/super() call in constructors"). */ private DefaultOperationMethod(final Map properties, final Map subProperties, final Integer sourceDimension, final Integer targetDimension, ParameterDescriptorGroup parameters) { super(properties, subProperties, LOCALIZABLES); Object formula = subProperties.get(FORMULA_KEY); if (formula != null) { if (formula instanceof Citation) { formula = new DefaultFormula((Citation) formula); } else if (formula instanceof CharSequence) { formula = new DefaultFormula((CharSequence) formula); } else if (!(formula instanceof Formula)) { throw new InvalidParameterValueException(Errors.format(Errors.Keys.ILLEGAL_ARGUMENT_$2, "formula", formula), "formula", formula); } } this.formula = (Formula) formula; // 'parameters' may be null, which is okay. A null value will // make serialization smaller and faster than an empty object. this.parameters = parameters; this.sourceDimension = sourceDimension; this.targetDimension = targetDimension; checkDimension(); } /** * Checks the validity of source and target dimensions. */ private void checkDimension() { if (sourceDimension != null) ensurePositive("sourceDimension", sourceDimension); if (targetDimension != null) ensurePositive("targetDimension", targetDimension); } /** * Formula(s) or procedure used by this operation method. This may be a reference to a * publication. Note that the operation method may not be analytic, in which case this * attribute references or contains the procedure, not an analytic formula. */ @Override public Formula getFormula() { return formula; } /** * Number of dimensions in the source CRS of this operation method. * May be null if unknown, as in an Affine Transform. * */ @Override public Integer getSourceDimensions() { return sourceDimension; } /** * Number of dimensions in the target CRS of this operation method. * May be null if unknown, as in an Affine Transform. */ @Override public Integer getTargetDimensions() { return targetDimension; } /** * Returns the set of parameters. */ @Override public ParameterDescriptorGroup getParameters() { return (parameters != null) ? parameters : Parameters.EMPTY_GROUP; } /** * Returns the operation type. Current implementation returns {@code Projection.class} for * proper WKT formatting using an unknown implementation. But the {@link MathTransformProvider} * subclass (with protected access) will overrides this method with a more conservative default * value. * * @return The GeoAPI interface implemented by this operation. */ Class getOperationType() { return Projection.class; } /** * Compares this operation method 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 #getFormula() formula}. * * @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 @SuppressWarnings("fallthrough") public boolean equals(final Object object, final ComparisonMode mode) { if (object == this) { return true; // Slight optimization. } if (super.equals(object, mode)) { switch (mode) { case BY_CONTRACT: { if (!Utilities.equals(getFormula(), ((OperationMethod) object).getFormula())) { return false; } // Fall through } default: { final OperationMethod that = (OperationMethod) object; return Utilities.equals(getSourceDimensions(), that.getSourceDimensions()) && Utilities.equals(getTargetDimensions(), that.getTargetDimensions()) && Utilities.deepEquals(getParameters(), that.getParameters(), mode); } case STRICT: { final DefaultOperationMethod that = (DefaultOperationMethod) object; return Utilities.equals(this.formula, that.formula) && Utilities.equals(this.sourceDimension, that.sourceDimension) && Utilities.equals(this.targetDimension, that.targetDimension) && Utilities.equals(this.parameters, that.parameters); } } } return false; } /** * {@inheritDoc} */ @Override protected int computeHashCode() { return hash(sourceDimension, hash(targetDimension, hash(parameters, super.computeHashCode()))); } /** * Formats the inner part of a * Well * Known Text (WKT) element. * * @param formatter The formatter to use. * @return The WKT element name. */ @Override public String formatWKT(final Formatter formatter) { if (Projection.class.isAssignableFrom(getOperationType())) { return "PROJECTION"; } return super.formatWKT(formatter); } /** * Returns {@code true} if the specified transform is likely to exists only for axis switch * and/or unit conversions. The heuristic rule checks if the transform is backed by a square * matrix with exactly one non-null value in each row and each column. This method is used * for implementation of the {@link #checkDimensions} method only. */ private static boolean isTrivial(final MathTransform transform) { if (transform instanceof LinearTransform) { final Matrix matrix = ((LinearTransform) transform).getMatrix(); final int size = matrix.getNumRow(); if (matrix.getNumCol() == size) { for (int j=0; j * This convenience method is provided for argument checking. * * @param method The operation method to compare to the math transform, or {@code null}. * @param transform The math transform to compare to the operation method, or {@code null}. * @throws MismatchedDimensionException if the number of dimensions are incompatibles. * * @todo The check for {@link ConcatenatedTransform} and {@link PassThroughTransform} works * only for Geotk implementations. */ public static void checkDimensions(final OperationMethod method, MathTransform transform) throws MismatchedDimensionException { if (method == null || transform == null) { return; } Integer expected = method.getSourceDimensions(); if (expected == null) { return; } int actual; while ((actual = transform.getSourceDimensions()) > expected.intValue()) { if (transform instanceof ConcatenatedTransform) { // Ignore axis switch and unit conversions. final ConcatenatedTransform c = (ConcatenatedTransform) transform; if (isTrivial(c.transform1)) { transform = c.transform2; } else if (isTrivial(c.transform2)) { transform = c.transform1; } else { // The transform is something more complex than an axis switch. // Stop the loop with the current illegal transform and let the // exception be thrown after the loop. break; } } else if (transform instanceof PassThroughTransform) { transform = ((PassThroughTransform) transform).getSubTransform(); } else { break; } } final String name; if (actual != expected.intValue()) { name = "sourceDimension"; } else { expected = method.getTargetDimensions(); if (expected == null) { return; } actual = transform.getTargetDimensions(); if (actual != expected.intValue()) { name = "targetDimension"; } else { return; } } throw new IllegalArgumentException(Errors.format( Errors.Keys.MISMATCHED_DIMENSION_$3, name, actual, expected)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy