org.geotoolkit.referencing.operation.DefaultOperationMethod Maven / Gradle / Ivy
Show all versions of geotk-referencing Show documentation
/*
* 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 name
* Value type
* Value 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 extends SingleOperation> 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));
}
}