| /* |
| * 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); |
| } |
| } |