blob: 6bd499a7422ce7e4b5c299811ae62c001f0cfc36 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.internal.referencing.provider;
import java.util.Map;
import java.util.Arrays;
import java.util.Collections;
import javax.xml.bind.annotation.XmlTransient;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.operation.OperationMethod;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
import org.apache.sis.parameter.TensorParameters;
import org.apache.sis.referencing.NamedIdentifier;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.transform.MathTransforms;
/**
* The provider for <cite>"Affine parametric transformation"</cite> (EPSG:9624).
* The set of available parameters depends on the matrix size, which is 3×3 by default.
*
* <table class="sis">
* <caption>{@code Affine} parameters</caption>
* <tr><th>EPSG code</th><th>EPSG name</th><th>OGC name</th><th>Default value</th></tr>
* <tr><td> </td> <td> </td> <td>{@code num_row}</td> <td>3</td></tr>
* <tr><td> </td> <td> </td> <td>{@code num_col}</td> <td>3</td></tr>
* <tr><td>8623</td> <td>{@code A0}</td> <td>{@code elt_0_0}</td> <td>1</td></tr>
* <tr><td>8624</td> <td>{@code A1}</td> <td>{@code elt_0_1}</td> <td>0</td></tr>
* <tr><td>8625</td> <td>{@code A2}</td> <td>{@code elt_0_2}</td> <td>0</td></tr>
* <tr><td>8639</td> <td>{@code B0}</td> <td>{@code elt_1_0}</td> <td>0</td></tr>
* <tr><td>8640</td> <td>{@code B1}</td> <td>{@code elt_1_1}</td> <td>1</td></tr>
* <tr><td>8641</td> <td>{@code B2}</td> <td>{@code elt_1_2}</td> <td>0</td></tr>
* <tr><td> </td> <td> </td> <td>{@code elt_2_0}</td> <td>0</td></tr>
* <tr><td> </td> <td> </td> <td>{@code elt_2_1}</td> <td>0</td></tr>
* <tr><td> </td> <td> </td> <td>{@code elt_2_2}</td> <td>1</td></tr>
* </table>
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 0.8
* @since 0.5
* @module
*/
@XmlTransient
public final class Affine extends AbstractProvider {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = 649555815622129472L;
/**
* The operation method name as defined in the EPSG database.
* Must matches exactly the EPSG name (this will be verified by JUnit tests).
*
* <p>Note: in contrast, the name used by OGC is just "Affine".</p>
*
* @see org.apache.sis.internal.util.Constants#AFFINE
*/
public static final String NAME = "Affine parametric transformation";
/**
* The EPSG:9624 compliant instance, created when first needed.
*/
private static volatile Affine EPSG_METHOD;
/**
* The number of dimensions used by the EPSG:9624 definition. This will be used as the
* default number of dimensions. Operation methods of other dimensions, where we have
* no EPSG definition, shall use the Well Known Text (WKT) parameter names.
*/
public static final int EPSG_DIMENSION = 2;
/**
* The maximal number of dimensions to be cached. Descriptors having more than
* this amount of dimensions will be recreated every time they are requested.
*/
private static final int MAX_CACHED_DIMENSION = 6;
/**
* Cached providers for methods of dimension 1×1 to {@link #MAX_CACHED_DIMENSION}.
* The index of each element is computed by {@link #cacheIndex(int, int)}.
* All usages of this array shall be synchronized on {@code cached}.
*/
private static final Affine[] cached = new Affine[MAX_CACHED_DIMENSION * MAX_CACHED_DIMENSION];
/**
* A map containing identification properties for creating {@code OperationMethod} or
* {@code ParameterDescriptorGroup} instances.
*/
private static final Map<String,?> IDENTIFICATION_EPSG, IDENTIFICATION_OGC;
static {
final NamedIdentifier nameOGC = new NamedIdentifier(Citations.OGC, Constants.OGC, Constants.AFFINE, null, null);
IDENTIFICATION_OGC = Collections.singletonMap(NAME_KEY, nameOGC);
IDENTIFICATION_EPSG = EPSGName.properties(9624, NAME, nameOGC);
}
/**
* Creates a provider for affine transform with a default matrix size (standard EPSG:9624 instance).
* This constructor is public for the needs of {@link java.util.ServiceLoader} — do not invoke explicitly.
* If an instance of {@code Affine()} is desired, invoke {@code getProvider(EPSG_DIMENSION, EPSG_DIMENSION)}
* instead.
*
* @see org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory
*/
public Affine() {
super(IDENTIFICATION_EPSG, EPSG_DIMENSION, EPSG_DIMENSION, new Descriptor(IDENTIFICATION_EPSG,
Arrays.copyOfRange( // Discards param 0 and 1, take only the ones in index range [2…7].
TensorParameters.ALPHANUM.getAllDescriptors(EPSG_DIMENSION, EPSG_DIMENSION + 1), 2, 8)));
/*
* Do caching ourselves because this constructor is usually not invoked by getProvider(int, int).
* It is usually invoked when DefaultMathTransformFactory scans the classpath with a ServiceLoader.
* This normally happen only once, so this instance is probably the unique instance to keep in the JVM.
*/
EPSG_METHOD = this;
}
/**
* Creates a provider for affine transform with the specified dimensions.
* This is created when first needed by {@link #getProvider(int, int, boolean)}.
*
* @see #getProvider(int, int, boolean)
*/
private Affine(final int sourceDimensions, final int targetDimensions) {
super(IDENTIFICATION_OGC, sourceDimensions, targetDimensions, new Descriptor(IDENTIFICATION_OGC,
TensorParameters.WKT1.getAllDescriptors(targetDimensions + 1, sourceDimensions + 1)));
}
/**
* The parameter descriptor to be returned by {@link Affine#getParameters()}.
* The only purpose of this class is to override the {@link #createValue()} method.
*/
private static final class Descriptor extends DefaultParameterDescriptorGroup {
/** For cross-version compatibility. */
private static final long serialVersionUID = 8320799650519834830L;
/** Creates a new descriptor for the given parameters. */
Descriptor(final Map<String,?> properties, final ParameterDescriptor<?>[] parameters) {
super(properties, 1, 1, parameters);
}
/**
* Returns default parameter values for the "Affine" operation. Unconditionally use the WKT1 parameter names,
* regardless of whether this descriptor is for the EPSG:9624 case, because the caller is free to change the
* matrix size, in which case (strictly speaking) the parameters would no longer be for EPSG:9624 operation.
*/
@Override
public ParameterValueGroup createValue() {
return TensorParameters.WKT1.createValueGroup(IDENTIFICATION_OGC);
}
}
/*
* Do not override the 'getOperationType()' method. We want to inherit the super-type value, which is
* SingleOperation.class, because we do not know if this operation method will be used for a Conversion
* or a Transformation. When applied on geocentric coordinates, this method applies a transformation
* (indeeded, the EPSG method name is "Affine parametric transformation"). But this method can also
* be applied for unit conversions or axis swapping for examples, which are conversions.
*/
/**
* The inverse of this operation can be described by the same operation with different parameter values.
*
* @return {@code true} for all {@code Affine}.
*/
@Override
public final boolean isInvertible() {
return true;
}
/**
* Creates a projective transform from the specified group of parameter values.
*
* @param factory ignored (can be null).
* @param values the group of parameter values.
* @return the created math transform.
* @throws ParameterNotFoundException if a required parameter was not found.
*/
@Override
public MathTransform createMathTransform(final MathTransformFactory factory, final ParameterValueGroup values)
throws ParameterNotFoundException
{
/*
* The TensorParameters constant used below (WKT1 or EPSG) does not matter,
* since both of them understand the names of the other TensorParameters.
*/
return MathTransforms.linear(TensorParameters.WKT1.toMatrix(values));
}
/**
* Returns the same operation method, but for different dimensions.
*
* @param sourceDimensions the desired number of input dimensions.
* @param targetDimensions the desired number of output dimensions.
* @return the redimensioned operation method, or {@code this} if no change is needed.
*/
@Override
public OperationMethod redimension(final int sourceDimensions, final int targetDimensions) {
return getProvider(sourceDimensions, targetDimensions, false);
}
/**
* Returns the index where to store a method of the given dimensions in the {@link #cached} array,
* or -1 if it should not be cached.
*/
private static int cacheIndex(int sourceDimensions, int targetDimensions) {
if (--sourceDimensions >= 0 && sourceDimensions < MAX_CACHED_DIMENSION &&
--targetDimensions >= 0 && targetDimensions < MAX_CACHED_DIMENSION)
{
return sourceDimensions * MAX_CACHED_DIMENSION + targetDimensions;
}
return -1;
}
/**
* Returns the operation method for the specified source and target dimensions.
* This method provides different {@code Affine} instances for different dimensions.
*
* @param sourceDimensions the number of source dimensions.
* @param targetDimensions the number of target dimensions.
* @param isAffine {@code true} if the transform is affine.
* @return the provider for transforms of the given source and target dimensions.
*/
public static Affine getProvider(final int sourceDimensions, final int targetDimensions, final boolean isAffine) {
Affine method;
if (isAffine && sourceDimensions == EPSG_DIMENSION && targetDimensions == EPSG_DIMENSION) {
/*
* Matrix complies with EPSG:9624 definition. This is the most common case. We do perform synchronization
* for this field since it is okay if the same object is created twice (they should be identical).
*/
method = EPSG_METHOD;
if (method == null) {
method = new Affine();
}
} else {
/*
* All other cases. We will use the WKT1 parameter names instead than the EPSG ones.
*/
final int index = cacheIndex(sourceDimensions, targetDimensions);
if (index >= 0) {
synchronized (cached) {
method = cached[index];
}
if (method != null) {
return method;
}
}
/*
* At this point, no existing instance has been found in the cache.
* Create a new instance and cache it if its dimension is not too large.
*/
method = new Affine(sourceDimensions, targetDimensions);
if (index >= 0) {
synchronized (cached) {
final Affine other = cached[index]; // May have been created in another thread.
if (other != null) {
return other;
}
cached[index] = method;
}
}
}
return method;
}
/**
* Returns parameter values for an identity transform of the given input and output dimensions.
* Callers can modify the returned parameters if desired.
*
* @param dimension the number of source and target dimensions.
* @return parameters for an identity transform of the given dimensions.
*
* @since 0.8
*/
public static ParameterValueGroup identity(int dimension) {
final ParameterValueGroup values = TensorParameters.WKT1.createValueGroup(
Collections.singletonMap(NAME_KEY, Constants.AFFINE));
values.parameter(Constants.NUM_COL).setValue(++dimension);
values.parameter(Constants.NUM_ROW).setValue( dimension);
return values;
}
/**
* Returns the parameter values for the given matrix. This method is invoked by implementations of
* {@link org.apache.sis.referencing.operation.transform.AbstractMathTransform#getParameterValues()}.
*
* @param matrix the matrix for which to get parameter values.
* @return the parameters of the given matrix.
*/
public static ParameterValueGroup parameters(final Matrix matrix) {
final int sourceDimensions = matrix.getNumCol() - 1;
final int targetDimensions = matrix.getNumRow() - 1;
final TensorParameters<Double> parameters;
final Map<String,?> properties;
if (sourceDimensions == EPSG_DIMENSION && targetDimensions == EPSG_DIMENSION && Matrices.isAffine(matrix)) {
parameters = TensorParameters.ALPHANUM;
properties = IDENTIFICATION_EPSG;
} else {
parameters = TensorParameters.WKT1;
properties = IDENTIFICATION_OGC;
}
return parameters.createValueGroup(properties, matrix);
}
}