/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-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. */ package org.geotools.referencing.operation.transform; import java.util.Collections; import javax.measure.unit.SI; import javax.measure.unit.NonSI; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.parameter.ParameterNotFoundException; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.Transformation; import org.geotools.util.Utilities; import org.geotools.measure.Units; import org.geotools.metadata.iso.citation.Citations; import org.geotools.parameter.DefaultParameterDescriptor; import org.geotools.parameter.FloatParameter; import org.geotools.parameter.ParameterGroup; import org.geotools.referencing.NamedIdentifier; import org.geotools.referencing.datum.BursaWolfParameters; import org.geotools.referencing.operation.MathTransformProvider; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; /** * An affine transform applied on {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric} * coordinates. While "geocentric translation" is a little bit more restrictive name, it describes * the part which is common to all instances of this class. A rotation may also be performed in * addition of the translation, but the rotation sign is operation-dependent (EPSG 9606 and 9607 * have opposite sign). This transform is used for the following operations: * <p> * <table border="1"> * <tr><th>EPSG name</th> <th>EPSG code</th></tr> * <tr><td>Geocentric translations</td> <td>9603</td></tr> * <tr><td>Position Vector 7-param. transformation</td> <td>9606</td></tr> * <tr><td>Coordinate Frame rotation</td> <td>9607</td></tr> * </table> * <p> * The conversion between geographic and geocentric coordinates is usually <strong>not</strong> * part of this transform. However, the Geotools implementation of the * {@linkplain GeocentricTranslation.Provider provider} accepts the following extensions: * <p> * <ul> * <li>If {@code "src_semi_major"} and {@code "src_semi_minor"} parameters are provided, then * a {@code "Ellipsoid_To_Geocentric"} transform is concatenated before this transform.</li> * <li>If {@code "tgt_semi_major"} and {@code "tgt_semi_minor"} parameters are provided, then * a {@code "Geocentric_To_Ellipsoid"} transform is concatenated after this transform.</li> * </ul> * * @since 2.2 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public class GeocentricTranslation extends ProjectiveTransform { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -168669443433018655L; /** * The parameter descriptor group. */ private final ParameterDescriptorGroup descriptor; /** * Creates a new geocentric affine transform. If the parameters don't contain rotation terms, * then this transform will be of kind "<cite>Geocentric translations</cite>". Otherwise, it * will be of kind "<cite>Position Vector 7-param. transformation</cite>". * * @param parameters The Bursa-Wolf parameters to use for initializing the transformation. */ public GeocentricTranslation(final BursaWolfParameters parameters) { this(parameters, parameters.isTranslation() ? Provider.PARAMETERS : ProviderSevenParam.PARAMETERS); } /** * Creates a new geocentric affine transform using the specified parameter descriptor. */ GeocentricTranslation(final BursaWolfParameters parameters, final ParameterDescriptorGroup descriptor) { super(parameters.getAffineTransform()); this.descriptor = descriptor; } /** * Creates a new geocentric affine transform using the specified parameter descriptor. */ private GeocentricTranslation(final Matrix matrix, final ParameterDescriptorGroup descriptor) { super(matrix); this.descriptor = descriptor; } /** * Returns the parameter descriptors for this math transform. */ @Override public ParameterDescriptorGroup getParameterDescriptors() { return descriptor; } /** * Returns the parameters for this math transform. */ @Override public ParameterValueGroup getParameterValues() { final BursaWolfParameters parameters = new BursaWolfParameters(null); parameters.setAffineTransform(getMatrix(), Double.POSITIVE_INFINITY); if (ProviderFrameRotation.PARAMETERS.equals(descriptor)) { parameters.ex = -parameters.ex; parameters.ey = -parameters.ey; parameters.ez = -parameters.ez; } final boolean isTranslation = Provider.PARAMETERS.equals(descriptor); final FloatParameter[] param = new FloatParameter[isTranslation ? 3 : 7]; param[0] = new FloatParameter(Provider.DX, parameters.dx); param[1] = new FloatParameter(Provider.DY, parameters.dy); param[2] = new FloatParameter(Provider.DZ, parameters.dz); if (!isTranslation) { param[3] = new FloatParameter(ProviderSevenParam.EX, parameters.ex); param[4] = new FloatParameter(ProviderSevenParam.EY, parameters.ey); param[5] = new FloatParameter(ProviderSevenParam.EZ, parameters.ez); param[6] = new FloatParameter(ProviderSevenParam.PPM, parameters.ppm); } return new ParameterGroup(getParameterDescriptors(), param); } /** * Creates an inverse transform using the specified matrix. */ @Override MathTransform createInverse(final Matrix matrix) { return new GeocentricTranslation(matrix, descriptor); } /** * Returns a hash value for this transform. * This value need not remain consistent between * different implementations of the same class. */ @Override public int hashCode() { return super.hashCode() ^ descriptor.hashCode(); } /** * Compares the specified object with this math transform for equality. */ @Override public boolean equals(final Object object) { if (super.equals(object)) { final GeocentricTranslation that = (GeocentricTranslation) object; return Utilities.equals(this.descriptor, that.descriptor); } return false; } /** * Base class for {@linkplain GeocentricTranslation geocentric affine transform} providers. * This base class is the provider for the "<cite>Geocentric translations</cite>" operation * (EPSG code 9603). The translation terms are the same for the 2 derived operations, * "<cite>Position Vector 7-param. transformation</cite>" and * "<cite>Coordinate Frame rotation</cite>". * * @version $Id$ * @author Martin Desruisseaux (IRD) * * @since 2.2 */ public static class Provider extends MathTransformProvider { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -7160250630666911608L; /** * The default value for geographic source and target dimensions, which is 2. * NOTE: If this default value is modified, then the handling of the 3D cases * in {@link MolodenskiTransform} must be adjusted. */ static final int DEFAULT_DIMENSION = 2; /** * The number of source geographic dimension (2 or 3). * This is a Geotools-specific argument. If presents, an {@code "Ellipsoid_To_Geocentric"} * transform will be concatenated before the geocentric translation. */ public static final ParameterDescriptor<Integer> SRC_DIM = DefaultParameterDescriptor.create( Collections.singletonMap(NAME_KEY, new NamedIdentifier(Citations.GEOTOOLS, "src_dim")), DEFAULT_DIMENSION, 2, 3, false); /** * The number of target geographic dimension (2 or 3). * This is a Geotools-specific argument. If presents, a {@code "Geocentric_To_Ellipsoid"} * transform will be concatenated after the geocentric translation. */ public static final ParameterDescriptor<Integer> TGT_DIM = DefaultParameterDescriptor.create( Collections.singletonMap(NAME_KEY, new NamedIdentifier(Citations.GEOTOOLS, "tgt_dim")), DEFAULT_DIMENSION, 2, 3, false); /** * The operation parameter descriptor for the "src_semi_major" optional parameter value. * This is a Geotools-specific argument. If presents, an {@code "Ellipsoid_To_Geocentric"} * transform will be concatenated before the geocentric translation. Valid values range * from 0 to infinity. */ public static final ParameterDescriptor<Double> SRC_SEMI_MAJOR = createOptionalDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "src_semi_major") }, 0.0, Double.POSITIVE_INFINITY, SI.METER); /** * The operation parameter descriptor for the "src_semi_minor" optional parameter value. * This is a Geotools-specific argument. If presents, an {@code "Ellipsoid_To_Geocentric"} * transform will be concatenated before the geocentric translation. Valid values range * from 0 to infinity. */ public static final ParameterDescriptor<Double> SRC_SEMI_MINOR = createOptionalDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "src_semi_minor"), }, 0.0, Double.POSITIVE_INFINITY, SI.METER); /** * The operation parameter descriptor for the "tgt_semi_major" optional parameter value. * This is a Geotools-specific argument. If presents, a {@code "Geocentric_To_Ellipsoid"} * transform will be concatenated after the geocentric translation. Valid values range * from 0 to infinity. */ public static final ParameterDescriptor<Double> TGT_SEMI_MAJOR = createOptionalDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "tgt_semi_major") }, 0.0, Double.POSITIVE_INFINITY, SI.METER); /** * The operation parameter descriptor for the "tgt_semi_minor" optional parameter value. * This is a Geotools-specific argument. If presents, a {@code "Geocentric_To_Ellipsoid"} * transform will be concatenated after the geocentric translation. Valid values range * from 0 to infinity. */ public static final ParameterDescriptor<Double> TGT_SEMI_MINOR = createOptionalDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "tgt_semi_minor") }, 0.0, Double.POSITIVE_INFINITY, SI.METER); /** * The operation parameter descriptor for the <cite>X-axis translation</cite> ("dx") * parameter value. Valid values range from -infinity to infinity. Units are meters. */ public static final ParameterDescriptor<Double> DX = createDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "dx"), new NamedIdentifier(Citations.EPSG, "X-axis translation") }, 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, SI.METER); /** * The operation parameter descriptor for the <cite>Y-axis translation</cite> ("dy") * parameter value. Valid values range from -infinity to infinity. Units are meters. */ public static final ParameterDescriptor<Double> DY = createDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "dy"), new NamedIdentifier(Citations.EPSG, "Y-axis translation") }, 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, SI.METER); /** * The operation parameter descriptor for the <cite>Z-axis translation</cite> ("dz") * parameter value. Valid values range from -infinity to infinity. Units are meters. */ public static final ParameterDescriptor<Double> DZ = createDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "dz"), new NamedIdentifier(Citations.EPSG, "Z-axis translation") }, 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, SI.METER); /** * The parameters group. */ static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] { new NamedIdentifier(Citations.EPSG, "Geocentric translations (geog2D domain)"), new NamedIdentifier(Citations.EPSG, "9603") }, new ParameterDescriptor[] { DX, DY, DZ, SRC_SEMI_MAJOR, SRC_SEMI_MINOR, TGT_SEMI_MAJOR, TGT_SEMI_MINOR, SRC_DIM, TGT_DIM }); /** * Constructs a default provider. */ public Provider() { this(PARAMETERS); } /** * Constructs a provider with the specified parameters. */ Provider(final ParameterDescriptorGroup parameters) { super(3, 3, parameters); } /** * Returns the operation type. */ @Override public Class<Transformation> getOperationType() { return Transformation.class; } /** * Creates a math transform from the specified group of parameter values. * * @param values The group of parameter values. * @return The created math transform. * @throws ParameterNotFoundException if a required parameter was not found. */ protected MathTransform createMathTransform(final ParameterValueGroup values) throws ParameterNotFoundException { final BursaWolfParameters parameters = new BursaWolfParameters(null); fill(parameters, values); return concatenate(concatenate(new GeocentricTranslation(parameters, getParameters()), values, SRC_SEMI_MAJOR, SRC_SEMI_MINOR, SRC_DIM), values, TGT_SEMI_MAJOR, TGT_SEMI_MINOR, TGT_DIM); } /** * Fill the specified Bursa-Wolf parameters according the specified values. * This method is invoked automatically by {@link #createMathTransform}. * * @param parameters The Bursa-Wold parameters to set. * @param values The parameter values to read. Those parameters will not be modified. */ protected void fill(final BursaWolfParameters parameters, final ParameterValueGroup values) { parameters.dx = doubleValue(DX, values); parameters.dy = doubleValue(DY, values); parameters.dz = doubleValue(DZ, values); } /** * Concatenates the supplied transform with an "ellipsoid to geocentric" or a * "geocentric to ellipsod" step, if needed. */ @SuppressWarnings("fallthrough") private static MathTransform concatenate(final MathTransform transform, final ParameterValueGroup values, final ParameterDescriptor major, final ParameterDescriptor minor, final ParameterDescriptor dim) { double semiMajor = doubleValue(major, values); double semiMinor = doubleValue(minor, values); int dimension = intValue(dim, values); switch (dimension) { case 0: if (Double.isNaN(semiMajor) && Double.isNaN(semiMinor)) return transform; case 2: // Fall through for 0 and 2 cases. case 3: break; // The dimension is a valid value. default: throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$2, dim.getName().getCode(), dimension)); } ensureValid(major, semiMajor); ensureValid(minor, semiMinor); final GeocentricTransform step; step = new GeocentricTransform(semiMajor, semiMinor, SI.METER, dimension==3); // Note: dimension may be 0 if not user-provided, which is treated as 2. if (dim == SRC_DIM) { return ConcatenatedTransform.create(step, transform); } else { return ConcatenatedTransform.create(transform, step.inverse()); } } /** * Ensures the the specified parameter is valid. */ private static void ensureValid(final ParameterDescriptor param, double value) { if (!(value > 0)) { throw new IllegalStateException(Errors.format(ErrorKeys.MISSING_PARAMETER_$1, param.getName().getCode())); } } } /** * Base class for {@linkplain GeocentricTranslation geocentric affine transform} providers * with rotation terms. This base class is the provider for the "<cite>Position Vector 7-param. * transformation</cite>". * * @version $Id$ * @author Martin Desruisseaux (IRD) * * @since 2.2 */ public static class ProviderSevenParam extends Provider { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -6398226638364450229L; /** * The maximal value for a rotation, in arc-second. */ private static final double MAX_ROTATION = 180*60*60; /** * The operation parameter descriptor for the <cite>X-axis rotation</cite> ("ex") * parameter value. Units are arc-seconds. */ public static final ParameterDescriptor EX = createDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "ex"), new NamedIdentifier(Citations.EPSG, "X-axis rotation") }, 0.0, -MAX_ROTATION, MAX_ROTATION, NonSI.SECOND_ANGLE); /** * The operation parameter descriptor for the <cite>Y-axis rotation</cite> ("ey") * parameter value. Units are arc-seconds. */ public static final ParameterDescriptor EY = createDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "ey"), new NamedIdentifier(Citations.EPSG, "Y-axis rotation") }, 0.0, -MAX_ROTATION, MAX_ROTATION, NonSI.SECOND_ANGLE); /** * The operation parameter descriptor for the <cite>Z-axis rotation</cite> ("ez") * parameter value. Units are arc-seconds. */ public static final ParameterDescriptor EZ = createDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "ez"), new NamedIdentifier(Citations.EPSG, "Z-axis rotation") }, 0.0, -MAX_ROTATION, MAX_ROTATION, NonSI.SECOND_ANGLE); /** * The operation parameter descriptor for the <cite>Scale difference</cite> ("ppm") * parameter value. Valid values range from -infinity to infinity. Units are parts * per million. */ public static final ParameterDescriptor PPM = createDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.OGC, "ppm"), new NamedIdentifier(Citations.EPSG, "Scale difference") }, 0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Units.PPM); /** * The parameters group. */ static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup("Position Vector transformation (geog2D domain)", "9606"); /** * Creates a parameters group. */ static ParameterDescriptorGroup createDescriptorGroup(final String name, final String code) { return createDescriptorGroup(new NamedIdentifier[] { new NamedIdentifier(Citations.EPSG, name), new NamedIdentifier(Citations.EPSG, code) }, new ParameterDescriptor[] { DX, DY, DZ, EX, EY, EZ, PPM, SRC_SEMI_MAJOR, SRC_SEMI_MINOR, TGT_SEMI_MAJOR, TGT_SEMI_MINOR, SRC_DIM, TGT_DIM }); } /** * Constructs a default provider. */ public ProviderSevenParam() { super(PARAMETERS); } /** * Constructs a provider with the specified parameters. */ ProviderSevenParam(final ParameterDescriptorGroup parameters) { super(parameters); } /** * Fills the specified Bursa-Wolf parameters according the specified values. */ @Override protected void fill(final BursaWolfParameters parameters, final ParameterValueGroup values) { super.fill(parameters, values); parameters.ppm = doubleValue(PPM, values); parameters.ex = doubleValue(EX, values); parameters.ey = doubleValue(EY, values); parameters.ez = doubleValue(EZ, values); } } /** * {@linkplain GeocentricTranslation Geocentric affine transform} provider for * "<cite>Coordinate Frame rotation</cite>". * * @version $Id$ * @author Martin Desruisseaux (IRD) * * @since 2.2 */ public static class ProviderFrameRotation extends ProviderSevenParam { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = 5513675854809530038L; /** * The parameters group. */ static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup("Coordinate Frame Rotation (geog2D domain)", "9607"); /** * Constructs a default provider. */ public ProviderFrameRotation() { super(PARAMETERS); } /** * Fills the specified Bursa-Wolf parameters according the specified values. */ @Override protected void fill(final BursaWolfParameters parameters, final ParameterValueGroup values) { super.fill(parameters, values); parameters.ex = -parameters.ex; parameters.ey = -parameters.ey; parameters.ez = -parameters.ez; } } }