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

org.geotoolkit.referencing.operation.AbstractCoordinateOperation 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.Collection;
import java.util.Collections;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import javax.measure.quantity.Length;
import net.jcip.annotations.Immutable;

import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.quality.Result;
import org.opengis.metadata.quality.QuantitativeResult;
import org.opengis.metadata.quality.PositionalAccuracy;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.*; // We really use most of them.
import org.opengis.referencing.IdentifiedObject;
import org.opengis.util.InternationalString;
import org.opengis.util.Record;

import org.geotoolkit.util.Utilities;
import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.io.wkt.Formatter;
import org.geotoolkit.referencing.AbstractIdentifiedObject;
import org.geotoolkit.metadata.iso.quality.AbstractPositionalAccuracy;
import org.geotoolkit.internal.referencing.Semaphores;
import org.geotoolkit.measure.Units;

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.internal.InternalUtilities.nonEmptySet;


/**
 * Establishes an association between a source and a target
 * {@linkplain CoordinateReferenceSystem coordinate reference system}, and provides a
 * {@linkplain MathTransform transform} for transforming coordinates in the source CRS
 * to coordinates in the target CRS. Many but not all coordinate operations (from CRS
 * A to CRS B) also uniquely define the inverse operation (from
 * CRS B to CRS A). In some cases, the operation method algorithm
 * for the inverse operation is the same as for the forward algorithm, but the signs of
 * some operation parameter values must be reversed. In other cases, different algorithms
 * are required for the forward and inverse operations, but the same operation parameter
 * values are used. If (some) entirely different parameter values are needed, a different
 * coordinate operation shall be defined.
 * 

* This class is conceptually abstract, even if it is technically possible to * instantiate it. Typical applications should create instances of the most specific subclass with * {@code Default} prefix instead. An exception to this rule may occurs when it is not possible to * identify the exact type. * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.18 * * @since 1.2 * @module */ @Immutable public class AbstractCoordinateOperation extends AbstractIdentifiedObject implements CoordinateOperation { /** * Serial number for inter-operability with different versions. */ private static final long serialVersionUID = 1237358357729193885L; /** * An empty array of positional accuracy. This is useful for fetching accuracies as an array, * using the following idiom: * * {@preformat java * getCoordinateOperationAccuracy().toArray(EMPTY_ACCURACY_ARRAY); * } * * @see #getCoordinateOperationAccuracy() */ public static final PositionalAccuracy[] EMPTY_ACCURACY_ARRAY = new PositionalAccuracy[0]; /** * List of localizable properties. To be given to {@link AbstractIdentifiedObject} constructor. */ private static final String[] LOCALIZABLES = {SCOPE_KEY}; /** * The source CRS, or {@code null} if not available. */ protected final CoordinateReferenceSystem sourceCRS; /** * The target CRS, or {@code null} if not available. */ protected final CoordinateReferenceSystem targetCRS; /** * Version of the coordinate transformation * (i.e., instantiation due to the stochastic nature of the parameters). */ final String operationVersion; /** * Estimate(s) of the impact of this operation on point accuracy, or {@code null} * if none. */ private final Collection coordinateOperationAccuracy; /** * Area in which this operation is valid, or {@code null} if not available. */ protected final Extent domainOfValidity; /** * Description of domain of usage, or limitations of usage, for which this operation is valid. */ private final InternationalString scope; /** * Transform from positions in the {@linkplain #getSourceCRS source coordinate reference system} * to positions in the {@linkplain #getTargetCRS target coordinate reference system}. */ protected final MathTransform transform; /** * Constructs a new coordinate operation with the same values than the specified * defining conversion, together with the specified source and target CRS. This * constructor is used by {@link DefaultConversion} only. */ AbstractCoordinateOperation(final Conversion definition, final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS, final MathTransform transform) { super(definition); this.sourceCRS = sourceCRS; this.targetCRS = targetCRS; this.operationVersion = definition.getOperationVersion(); this.coordinateOperationAccuracy = definition.getCoordinateOperationAccuracy(); this.domainOfValidity = definition.getDomainOfValidity(); this.scope = definition.getScope(); this.transform = transform; } /** * Constructs a coordinate operation from a set of properties. * 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.CoordinateOperation#OPERATION_VERSION_KEY}  {@link String}  {@link #getOperationVersion}
 {@value org.opengis.referencing.operation.CoordinateOperation#COORDINATE_OPERATION_ACCURACY_KEY}  {@linkplain PositionalAccuracy} (singleton or array)  {@link #getCoordinateOperationAccuracy}
 {@value org.opengis.referencing.operation.CoordinateOperation#DOMAIN_OF_VALIDITY_KEY}  {@link Extent}  {@link #getDomainOfValidity}
 {@value org.opengis.referencing.operation.CoordinateOperation#SCOPE_KEY}  {@link String} or {@link InternationalString}  {@link #getScope}
* * @param properties Set of properties. Should contains at least {@code "name"}. * @param sourceCRS The source CRS. * @param targetCRS The target CRS. * @param transform Transform from positions in the {@linkplain #getSourceCRS source CRS} * to positions in the {@linkplain #getTargetCRS target CRS}. */ public AbstractCoordinateOperation(final Map properties, final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS, final MathTransform transform) { this(properties, new HashMap(), sourceCRS, targetCRS, transform); } /** * Work around for RFE #4093999 in Sun's bug database * ("Relax constraint on placement of this()/super() call in constructors"). */ private AbstractCoordinateOperation(final Map properties, final Map subProperties, final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS, final MathTransform transform) { super(properties, subProperties, LOCALIZABLES); Object positionalAccuracy; domainOfValidity = (Extent) subProperties.get(DOMAIN_OF_VALIDITY_KEY); scope = (InternationalString) subProperties.get(SCOPE_KEY); operationVersion = (String) subProperties.get(OPERATION_VERSION_KEY); positionalAccuracy = subProperties.get(COORDINATE_OPERATION_ACCURACY_KEY); if (positionalAccuracy instanceof PositionalAccuracy[]) { final PositionalAccuracy[] accuracies = ((PositionalAccuracy[]) positionalAccuracy).clone(); for (int i=0; iemptySet() : Collections.singleton((PositionalAccuracy) positionalAccuracy); } this.sourceCRS = sourceCRS; this.targetCRS = targetCRS; this.transform = transform; validate(); } /** * Checks the validity of this operation. This method is invoked by the constructor after * every fields have been assigned. It can be overridden by subclasses if different rules * should be applied. *

* {@link DefaultConversion} overrides this method in order to allow null values, providing * that all of {@code transform}, {@code sourceCRS} and {@code targetCRS} are null together. * Note that null values are not allowed for transformations, so {@link DefaultTransformation} * does not override this method. * * @throws IllegalArgumentException if at least one of {@code transform}, {@code sourceCRS} * or {@code targetCRS} is invalid. We throw this kind of exception rather than * {@link IllegalStateException} because this method is invoked by the constructor * for checking argument validity. */ void validate() throws IllegalArgumentException { ensureNonNull ("sourceCRS", sourceCRS); ensureNonNull ("targetCRS", targetCRS); ensureNonNull ("transform", transform); checkDimension("sourceCRS", sourceCRS, transform.getSourceDimensions()); checkDimension("targetCRS", targetCRS, transform.getTargetDimensions()); } /** * Checks if a reference coordinate system has the expected number of dimensions. * * @param name The argument name. * @param crs The coordinate reference system to check. * @param expected The expected number of dimensions. */ private static void checkDimension(final String name, final CoordinateReferenceSystem crs, final int expected) { final int actual = crs.getCoordinateSystem().getDimension(); if (actual != expected) { throw new IllegalArgumentException(Errors.format( Errors.Keys.MISMATCHED_DIMENSION_$3, name, actual, expected)); } } /** * Returns the source CRS. */ @Override public CoordinateReferenceSystem getSourceCRS() { return sourceCRS; } /** * Returns the target CRS. */ @Override public CoordinateReferenceSystem getTargetCRS() { return targetCRS; } /** * Version of the coordinate transformation (i.e., instantiation due to the stochastic * nature of the parameters). Mandatory when describing a transformation, and should not * be supplied for a conversion. * * @return The coordinate operation version, or {@code null} in none. */ @Override public String getOperationVersion() { return operationVersion; } /** * Estimate(s) of the impact of this operation on point accuracy. Gives * position error estimates for target coordinates of this coordinate * operation, assuming no errors in source coordinates. * * @return The position error estimates, or an empty collection if not available. * * @see #getAccuracy() * * @since 2.4 */ @Override public Collection getCoordinateOperationAccuracy() { if (coordinateOperationAccuracy == null) { return Collections.emptySet(); } return coordinateOperationAccuracy; } /** * Convenience method returning the accuracy in meters. The default implementation delegates * to {@linkplain #getAccuracy(CoordinateOperation) getAccuracy}(this). Subclasses * should override this method if they can provide a more accurate algorithm. * * @return The accuracy in meters, or NaN if unknown. * * @since 2.2 */ public double getAccuracy() { return accuracy(this); } /** * Convenience method returning the accuracy in meters for the specified operation. This method * try each of the following procedures and returns the first successful one: *

*

    *
  • If a {@linkplain QuantitativeResult quantitative} positional accuracy is found with a * linear unit, then this accuracy estimate is converted to {@linkplain SI#METRE metres} * and returned.
  • * *
  • Otherwise, if the operation is a {@linkplain Conversion conversion}, then returns * 0 since a conversion is by definition accurates up to rounding errors.
  • * *
  • Otherwise, if the operation is a {@linkplain Transformation transformation}, then * checks if the datum shift were applied with the help of Bursa-Wolf parameters. * This procedure looks for Geotk-specific * {@link AbstractPositionalAccuracy#DATUM_SHIFT_APPLIED DATUM_SHIFT_APPLIED} and * {@link AbstractPositionalAccuracy#DATUM_SHIFT_OMITTED DATUM_SHIFT_OMITTED} metadata. * If a datum shift has been applied, returns 25 meters. If a datum shift should have * been applied but has been omitted, returns 1000 meters. The 1000 meters value is * higher than the highest value (999 meters) found in the EPSG database version 6.7. * The 25 meters value is the next highest value found in the EPSG database for a * significant number of transformations. * *
  • Otherwise, if the operation is a {@linkplain ConcatenatedOperation concatenated one}, * returns the sum of the accuracy of all components. This is a conservative scenario * where we assume that errors cumulate linearly. Note that this is not necessarily * the "worst case" scenario since the accuracy could be worst if the math transforms * are highly non-linear.
  • *
* * @param operation The operation to inspect for accuracy. * @return The accuracy estimate (always in meters), or NaN if unknown. * * @since 2.2 */ public static double getAccuracy(final CoordinateOperation operation) { if (operation instanceof AbstractCoordinateOperation) { // Maybe the user overridden this method... return ((AbstractCoordinateOperation) operation).getAccuracy(); } return accuracy(operation); } /** * Implementation of {@code getAccuracy} methods, both the ordinary and the * static member variants. The {@link #getAccuracy()} method can't invoke * {@link #getAccuracy(CoordinateOperation)} directly since it would cause * never-ending recursive calls. */ private static double accuracy(final CoordinateOperation operation) { final Collection accuracies = operation.getCoordinateOperationAccuracy(); if (accuracies != null) for (final PositionalAccuracy accuracy : accuracies) { if (accuracy != null) for (final Result result : accuracy.getResults()) { if (result instanceof QuantitativeResult) { final QuantitativeResult quantity = (QuantitativeResult) result; final Collection records = quantity.getValues(); if (records != null) { final Unit unit = quantity.getValueUnit(); if (Units.isLinear(unit)) { final Unit unitOfLength = unit.asType(Length.class); for (final Record record : records) { for (final Object value : record.getAttributes().values()) { if (value instanceof Number) { double v = ((Number) value).doubleValue(); v = unitOfLength.getConverterTo(SI.METRE).convert(v); return v; } } } } } } } } /* * No quantitative, linear accuracy were found. If the coordinate operation is actually * a conversion, the accuracy is up to rounding error (i.e. conceptually 0) by definition. */ if (operation instanceof Conversion) { return 0; } /* * If the coordinate operation is actually a transformation, checks if Bursa-Wolf * parameters were available for the datum shift. This is Geotk-specific. * See javadoc for a rational about the return values chosen. */ if (operation instanceof Transformation) { if (!accuracies.contains(AbstractPositionalAccuracy.DATUM_SHIFT_OMITTED)) { if (accuracies.contains(AbstractPositionalAccuracy.DATUM_SHIFT_APPLIED)) { return 25; } } return 1000; } /* * If the coordinate operation is a compound of other coordinate operations, returns * the sum of their accuracy, skipping unknown ones. Making the sum is a conservative * approach (not exactly the "worst case" scenario, since it could be worst if the * transforms are highly non-linear). */ double accuracy = Double.NaN; if (operation instanceof ConcatenatedOperation) { for (final SingleOperation op : ((ConcatenatedOperation) operation).getOperations()) { final double candidate = Math.abs(getAccuracy(op)); if (!Double.isNaN(candidate)) { if (Double.isNaN(accuracy)) { accuracy = candidate; } else { accuracy += candidate; } } } } return accuracy; } /** * Area or region or timeframe in which this coordinate operation is valid. * Returns {@code null} if not available. * * @since 2.4 */ @Override public Extent getDomainOfValidity() { return domainOfValidity; } /** * Description of domain of usage, or limitations of usage, for which this operation is valid. */ @Override public InternationalString getScope() { return scope; } /** * Gets the math transform. The math transform will transform positions in the * {@linkplain #getSourceCRS source coordinate reference system} into positions * in the {@linkplain #getTargetCRS target coordinate reference system}. */ @Override public MathTransform getMathTransform() { return transform; } /** * Returns the most specific {@link CoordinateOperation} interface implemented by the * specified operation. Special cases: *

*

    *
  • If the operation implements the {@link Transformation} interface, * then this method returns {@code Transformation.class}. Transformation * has precedence over any other interface implemented by the operation.
  • *
  • Otherwise if the operation implements the {@link Conversion} interface, * then this method returns the most specific {@code Conversion} sub-interface.
  • *
  • Otherwise if the operation implements the {@link SingleOperation} interface, * then this method returns {@code SingleOperation.class}.
  • *
  • Otherwise if the operation implements the {@link ConcatenatedOperation} interface, * then this method returns {@code ConcatenatedOperation.class}.
  • *
  • Otherwise this method returns {@code CoordinateOperation.class}.
  • *
* * @param operation A coordinate operation. * @return The most specific GeoAPI interface implemented by the given operation. */ public static Class getType(final CoordinateOperation operation) { if (operation instanceof Transformation) return Transformation.class; if (operation instanceof ConicProjection) return ConicProjection.class; if (operation instanceof CylindricalProjection) return CylindricalProjection.class; if (operation instanceof PlanarProjection) return PlanarProjection.class; if (operation instanceof Projection) return Projection.class; if (operation instanceof Conversion) return Conversion.class; if (operation instanceof SingleOperation) return SingleOperation.class; if (operation instanceof ConcatenatedOperation) return ConcatenatedOperation.class; return CoordinateOperation.class; } /** * Compares this coordinate 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 @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) { // Do not test targetCRS now - it will be tested later in this method. // See comment in DefaultSingleOperation.equals(...) about why we compare MathTransform. case STRICT: { final AbstractCoordinateOperation that = (AbstractCoordinateOperation) object; if (!Utilities.equals(sourceCRS, that.sourceCRS) || !Utilities.equals(transform, that.transform) || !Utilities.equals(domainOfValidity, that.domainOfValidity) || !Utilities.equals(scope, that.scope) || !Utilities.equals(coordinateOperationAccuracy, that.coordinateOperationAccuracy)) { return false; } break; } case BY_CONTRACT: { final CoordinateOperation that = (CoordinateOperation) object; if (!deepEquals(getScope(), that.getScope(), mode) || !deepEquals(getDomainOfValidity(), that.getDomainOfValidity(), mode) || !deepEquals(getCoordinateOperationAccuracy(), that.getCoordinateOperationAccuracy(), mode)) { return false; } // Fall through } default: { final CoordinateOperation that = (CoordinateOperation) object; if (!deepEquals(getSourceCRS(), that.getSourceCRS(), mode) || !deepEquals(getMathTransform(), that.getMathTransform(), mode)) { return false; } break; } } /* * Avoid never-ending recursivity: AbstractDerivedCRS has a 'conversionFromBase' * field that is set to this AbstractCoordinateOperation. */ if (Semaphores.queryAndSet(Semaphores.COMPARING)) { return true; } try { if (mode == ComparisonMode.STRICT) { return Utilities.equals(targetCRS, ((AbstractCoordinateOperation) object).targetCRS); } else { return deepEquals(getTargetCRS(), ((CoordinateOperation) object).getTargetCRS(), mode); } } finally { Semaphores.clear(Semaphores.COMPARING); } } return false; } /** * {@inheritDoc} */ @Override protected int computeHashCode() { return hash(sourceCRS, hash(targetCRS, hash(transform, super.computeHashCode()))); } /** * Formats this operation as a pseudo-WKT format. No WKT format were defined for coordinate * operation at the time this method was written. This method may change in any future version * until a standard format is found. * * @param formatter The formatter to use. * @return The WKT element name. */ @Override public String formatWKT(final Formatter formatter) { append(formatter, sourceCRS, "SOURCE"); append(formatter, targetCRS, "TARGET"); return super.formatWKT(formatter); } /** * Appends the identifier for the specified object name (possibly {@code null}) to the specified * formatter. * * @param formatter The formatter where to append the object name. * @param object The object to append, or {@code null} if none. * @param type The label to put in front of the object name. */ @SuppressWarnings("serial") static void append(final Formatter formatter, final IdentifiedObject object, final String type) { if (object != null) { final Map properties = new HashMap(4); properties.put(IdentifiedObject.NAME_KEY, formatter.getName(object)); properties.put(IdentifiedObject.IDENTIFIERS_KEY, formatter.getIdentifier(object)); formatter.append((IdentifiedObject) new AbstractIdentifiedObject(properties) { @Override public String formatWKT(final Formatter formatter) { /* * Do not invoke super.formatWKT(formatter), since it doesn't do anything * more than invoking 'formatter.setInvalidWKT(...)' (we ignore the value * returned). This method will rather be invoked by the enclosing class. */ return type; } }); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy