| /* |
| * 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.referencing.operation.transform; |
| |
| import java.util.Map; |
| import java.util.List; |
| import java.util.Collections; |
| import java.awt.geom.AffineTransform; |
| import org.opengis.util.FactoryException; |
| import org.opengis.geometry.Envelope; |
| import org.opengis.geometry.DirectPosition; |
| import org.opengis.geometry.MismatchedDimensionException; |
| import org.opengis.referencing.operation.Matrix; |
| import org.opengis.referencing.operation.MathTransform; |
| import org.opengis.referencing.operation.MathTransform1D; |
| import org.opengis.referencing.operation.MathTransform2D; |
| import org.opengis.referencing.operation.TransformException; |
| import org.opengis.referencing.operation.MathTransformFactory; |
| import org.apache.sis.internal.referencing.DirectPositionView; |
| import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix; |
| import org.apache.sis.internal.referencing.j2d.AffineTransform2D; |
| import org.apache.sis.referencing.operation.matrix.AffineTransforms2D; |
| import org.apache.sis.referencing.operation.matrix.MatrixSIS; |
| import org.apache.sis.referencing.operation.matrix.Matrices; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.ArraysExt; |
| import org.apache.sis.util.Static; |
| |
| |
| /** |
| * Utility methods creating or working on {@link MathTransform} instances. |
| * This class centralizes in one place some of the most commonly used functions this package. |
| * The {@code MathTransforms} class provides the following services: |
| * |
| * <ul> |
| * <li>Create various SIS implementations of {@link MathTransform}.</li> |
| * <li>Perform non-standard operations on arbitrary instances.</li> |
| * </ul> |
| * |
| * The factory static methods are provided as convenient alternatives to the GeoAPI {@link MathTransformFactory} |
| * interface. However users seeking for more implementation neutrality are encouraged to limit themselves to the |
| * GeoAPI factory interfaces instead. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.0 |
| * |
| * @see MathTransformFactory |
| * |
| * @since 0.5 |
| * @module |
| */ |
| public final class MathTransforms extends Static { |
| /** |
| * Do not allow instantiation of this class. |
| */ |
| private MathTransforms() { |
| } |
| |
| /** |
| * Returns an identity transform of the specified dimension. |
| * |
| * <p>Special cases:</p> |
| * <ul> |
| * <li>If {@code dimension == 1}, then the returned transform implements {@link MathTransform1D}.</li> |
| * <li>If {@code dimension == 2}, then the returned transform implements {@link MathTransform2D}.</li> |
| * </ul> |
| * |
| * @param dimension number of dimensions of the transform to be returned. |
| * @return an identity transform of the specified dimension. |
| */ |
| public static LinearTransform identity(final int dimension) { |
| ArgumentChecks.ensurePositive("dimension", dimension); |
| return IdentityTransform.create(dimension); |
| } |
| |
| /** |
| * Creates an affine transform which applies the same translation for all dimensions. |
| * For each dimension, input values <var>x</var> are converted into output values <var>y</var> |
| * using the following equation: |
| * |
| * <blockquote><var>y</var> = <var>x</var> + {@code offset}</blockquote> |
| * |
| * @param dimension number of input and output dimensions. |
| * @param offset the {@code offset} term in the linear equation. |
| * @return an affine transform applying the specified translation. |
| * |
| * @since 1.0 |
| */ |
| public static LinearTransform uniformTranslation(final int dimension, final double offset) { |
| ArgumentChecks.ensurePositive("dimension", dimension); |
| if (offset == 0) { |
| return IdentityTransform.create(dimension); |
| } |
| switch (dimension) { |
| case 0: return IdentityTransform.create(0); |
| case 1: return LinearTransform1D.create(1, offset); |
| case 2: return new AffineTransform2D(1, 0, 0, 1, offset, offset); |
| default: return new TranslationTransform(dimension, offset); |
| } |
| } |
| |
| /** |
| * Creates an affine transform which applies the given translation. |
| * The source and target dimensions of the transform are the length of the given vector. |
| * |
| * @param vector the translation vector. |
| * @return an affine transform applying the specified translation. |
| * |
| * @since 1.0 |
| */ |
| public static LinearTransform translation(final double... vector) { |
| ArgumentChecks.ensureNonNull("vector", vector); |
| final LinearTransform tr; |
| switch (vector.length) { |
| case 0: return IdentityTransform.create(0); |
| case 1: return LinearTransform1D.create(1, vector[0]); |
| case 2: tr = new AffineTransform2D(1, 0, 0, 1, vector[0], vector[1]); break; |
| default: tr = new TranslationTransform(vector); break; |
| } |
| return tr.isIdentity() ? IdentityTransform.create(vector.length) : tr; |
| } |
| |
| /** |
| * Creates an affine transform which applies the given scale. |
| * The source and target dimensions of the transform are the length of the given vector. |
| * |
| * @param factors the scale factors. |
| * @return an affine transform applying the specified scales. |
| * |
| * @since 1.0 |
| */ |
| public static LinearTransform scale(final double... factors) { |
| ArgumentChecks.ensureNonNull("factors", factors); |
| final LinearTransform tr; |
| switch (factors.length) { |
| case 0: return IdentityTransform.create(0); |
| case 1: return LinearTransform1D.create(factors[0], 0); |
| case 2: tr = new AffineTransform2D(factors[0], 0, 0, factors[1], 0, 0); break; |
| default: tr = new ScaleTransform(factors); break; |
| } |
| return tr.isIdentity() ? IdentityTransform.create(factors.length) : tr; |
| } |
| |
| /** |
| * Creates a one-dimensional affine transform for the given coefficients. |
| * Input values <var>x</var> will be converted into output values <var>y</var> using the following equation: |
| * |
| * <blockquote><var>y</var> = <var>x</var> ⋅ {@code scale} + {@code offset}</blockquote> |
| * |
| * @param scale the {@code scale} term in the linear equation. |
| * @param offset the {@code offset} term in the linear equation. |
| * @return the linear transform for the given scale and offset. |
| * |
| * @see org.apache.sis.measure.Units#converter(Number, Number) |
| */ |
| public static LinearTransform linear(final double scale, final double offset) { |
| return LinearTransform1D.create(scale, offset); |
| } |
| |
| /** |
| * Creates an arbitrary linear transform from the specified matrix. Usually the matrix |
| * {@linkplain org.apache.sis.referencing.operation.matrix.MatrixSIS#isAffine() is affine}, |
| * but this is not mandatory. Non-affine matrix will define a projective transform. |
| * |
| * <p>If the transform input dimension is {@code M}, and output dimension is {@code N}, |
| * then the given matrix shall have size {@code [N+1][M+1]}. |
| * The +1 in the matrix dimensions allows the matrix to do a shift, as well as a rotation. |
| * The {@code [M][j]} element of the matrix will be the <var>j</var>'th coordinate of the moved origin.</p> |
| * |
| * @param matrix the matrix used to define the linear transform. |
| * @return the linear (usually affine) transform. |
| * |
| * @see #getMatrix(MathTransform) |
| * @see DefaultMathTransformFactory#createAffineTransform(Matrix) |
| */ |
| public static LinearTransform linear(final Matrix matrix) { |
| ArgumentChecks.ensureNonNull("matrix", matrix); |
| final int sourceDimension = matrix.getNumCol() - 1; |
| final int targetDimension = matrix.getNumRow() - 1; |
| if (sourceDimension == targetDimension) { |
| if (matrix.isIdentity()) { |
| return identity(sourceDimension); |
| } |
| if (Matrices.isAffine(matrix)) { |
| switch (sourceDimension) { |
| case 1: { |
| return linear(matrix.getElement(0,0), matrix.getElement(0,1)); |
| } |
| case 2: { |
| if (matrix instanceof ExtendedPrecisionMatrix) { |
| return new AffineTransform2D(((ExtendedPrecisionMatrix) matrix).getExtendedElements()); |
| } else { |
| return new AffineTransform2D( |
| matrix.getElement(0,0), matrix.getElement(1,0), |
| matrix.getElement(0,1), matrix.getElement(1,1), |
| matrix.getElement(0,2), matrix.getElement(1,2)); |
| } |
| } |
| } |
| } else if (sourceDimension == 2) { |
| return new ProjectiveTransform2D(matrix); |
| } |
| } |
| final LinearTransform candidate = CopyTransform.create(matrix); |
| if (candidate != null) { |
| return candidate; |
| } |
| return new ProjectiveTransform(matrix).optimize(); |
| } |
| |
| /** |
| * Returns a linear (usually affine) transform which approximates the given transform in the vicinity of the given position. |
| * If the given transform is already an instance of {@link LinearTransform}, then it is returned as-is. |
| * Otherwise an approximation for the given position is created using the |
| * {@linkplain MathTransform#derivative(DirectPosition) transform derivative} at that position. |
| * |
| * <p><b>Invariant:</b> transforming the given {@code position} using the given {@code transform} produces the same result |
| * (ignoring rounding error) than transforming the same {@code position} using the returned transform. This invariant holds |
| * only for that particular position; the transformation of any other positions may produce different results.</p> |
| * |
| * @param transform the transform to approximate by an affine transform. |
| * @param position position in source CRS around which to get the an affine transform approximation. |
| * @return a transform approximating the given transform around the given position. |
| * @throws TransformException if an error occurred while transforming the given position |
| * or computing the derivative at that position. |
| * |
| * @since 1.0 |
| * |
| * @see #getMatrix(MathTransform, DirectPosition) |
| */ |
| public static LinearTransform linear(final MathTransform transform, final DirectPosition position) throws TransformException { |
| if (transform instanceof LinearTransform) { |
| // We accept null position here for consistency with MathTransform.derivative(DirectPosition). |
| ArgumentChecks.ensureDimensionMatches("position", transform.getSourceDimensions(), position); |
| return (LinearTransform) transform; |
| } else { |
| return linear(getMatrix(transform, position)); |
| } |
| } |
| |
| /** |
| * Creates a transform for the <i>y=f(x)</i> function where <var>y</var> are computed by a linear interpolation. |
| * Both {@code preimage} (the <var>x</var>) and {@code values} (the <var>y</var>) arguments can be null: |
| * |
| * <ul> |
| * <li>If both {@code preimage} and {@code values} arrays are non-null, then they must have the same length.</li> |
| * <li>If both {@code preimage} and {@code values} arrays are null, then this method returns the identity transform.</li> |
| * <li>If only {@code preimage} is null, then the <var>x</var> values are taken as {0, 1, 2, …, {@code values.length} - 1}.</li> |
| * <li>If only {@code values} is null, then the <var>y</var> values are taken as {0, 1, 2, …, {@code preimage.length} - 1}.</li> |
| * </ul> |
| * |
| * All {@code preimage} elements shall be real numbers (not NaN) sorted in increasing or decreasing order. |
| * Elements in the {@code values} array do not need to be ordered, but the returned transform will be invertible |
| * only if all values are real numbers sorted in increasing or decreasing order. |
| * Furthermore the returned transform is affine (i.e. implement the {@link LinearTransform} interface) |
| * if the interval between each {@code preimage} and {@code values} element is constant. |
| * |
| * <p>The current implementation uses linear interpolation. This may be changed in a future SIS version.</p> |
| * |
| * @param preimage the input values (<var>x</var>) in the function domain, or {@code null}. |
| * @param values the output values (<var>y</var>) in the function range, or {@code null}. |
| * @return the <i>y=f(x)</i> function. |
| * |
| * @see org.opengis.coverage.InterpolationMethod |
| * |
| * @since 0.7 |
| */ |
| public static MathTransform1D interpolate(final double[] preimage, final double[] values) { |
| return LinearInterpolator1D.create(preimage, values); |
| } |
| |
| /** |
| * Creates a transform defined as one transform applied globally except in sub-areas where more accurate |
| * transforms are available. Such constructs appear in some datum shift files. The result of transforming |
| * a point by the returned {@code MathTransform} is as if iterating over all given {@link Envelope}s in |
| * no particular order, find the smallest one containing the point to transform (envelope border considered |
| * inclusive), then use the associated {@link MathTransform} for transforming the point. |
| * If the point is not found in any envelope, then the global transform is applied. |
| * |
| * <p>The following constraints apply:</p> |
| * <ul> |
| * <li>The global transform must be a reasonable approximation of the specialized transforms |
| * (this is required for calculating the inverse transform).</li> |
| * <li>All transforms in the {@code specializations} map must have the same number of source and target |
| * dimensions than the {@code global} transform.</li> |
| * <li>All envelopes in the {@code specializations} map must have the same number of dimensions |
| * than the global transform <em>source</em> dimensions.</li> |
| * <li>In current implementation, each envelope must either be fully included in another envelope, |
| * or not overlap any other envelope.</li> |
| * </ul> |
| * |
| * @param global the transform to use globally where there is no suitable specialization. |
| * @param specializations more accurate transforms available in some sub-areas. |
| * @return a transform applying the given global transform except in sub-areas where specializations are available. |
| * @throws IllegalArgumentException if a constraint is not met. |
| * |
| * @since 1.0 |
| */ |
| public static MathTransform specialize(final MathTransform global, final Map<Envelope,MathTransform> specializations) { |
| ArgumentChecks.ensureNonNull("generic", global); |
| ArgumentChecks.ensureNonNull("specializations", specializations); |
| if (specializations.isEmpty()) { |
| return global; |
| } else if (global.getSourceDimensions() == 2 && global.getTargetDimensions() == 2) { |
| return new SpecializableTransform2D(global, specializations); |
| } else { |
| return new SpecializableTransform(global, specializations); |
| } |
| } |
| |
| /** |
| * Creates a transform which passes through a subset of coordinates to another transform. |
| * This method returns a transform having the following dimensions: |
| * |
| * {@preformat java |
| * Source: firstAffectedCoordinate + subTransform.getSourceDimensions() + numTrailingCoordinates |
| * Target: firstAffectedCoordinate + subTransform.getTargetDimensions() + numTrailingCoordinates |
| * } |
| * |
| * Affected coordinates will range from {@code firstAffectedCoordinate} inclusive to |
| * {@code dimTarget - numTrailingCoordinates} exclusive. |
| * |
| * @param firstAffectedCoordinate index of the first affected coordinate. |
| * @param subTransform the sub-transform to apply on modified coordinates. |
| * @param numTrailingCoordinates number of trailing coordinates to pass through. |
| * @return a pass-through transform, potentially as a {@link PassThroughTransform} instance but not necessarily. |
| * |
| * @since 1.0 |
| */ |
| public static MathTransform passThrough(final int firstAffectedCoordinate, |
| final MathTransform subTransform, |
| final int numTrailingCoordinates) |
| { |
| ArgumentChecks.ensureNonNull ("subTransform", subTransform); |
| ArgumentChecks.ensurePositive("firstAffectedCoordinate", firstAffectedCoordinate); |
| ArgumentChecks.ensurePositive("numTrailingCoordinates", numTrailingCoordinates); |
| if (firstAffectedCoordinate == 0 && numTrailingCoordinates == 0) { |
| return subTransform; |
| } |
| if (subTransform.isIdentity()) { |
| final int dimension = subTransform.getSourceDimensions(); |
| if (dimension == subTransform.getTargetDimensions()) { |
| return IdentityTransform.create(firstAffectedCoordinate + dimension + numTrailingCoordinates); |
| } |
| } |
| return PassThroughTransform.create(firstAffectedCoordinate, subTransform, numTrailingCoordinates); |
| } |
| |
| /** |
| * Puts together a list of independent math transforms, each of them operating on a subset of coordinate values. |
| * This method is often used for defining 4-dimensional (<var>x</var>,<var>y</var>,<var>z</var>,<var>t</var>) |
| * transform as an aggregation of 3 simpler transforms operating on (<var>x</var>,<var>y</var>), (<var>z</var>) |
| * and (<var>t</var>) values respectively. |
| * |
| * <p>Invariants:</p> |
| * <ul> |
| * <li>The {@linkplain AbstractMathTransform#getSourceDimensions() source dimensions} of the returned transform |
| * is equals to the sum of the source dimensions of all given transforms.</li> |
| * <li>The {@linkplain AbstractMathTransform#getTargetDimensions() target dimensions} of the returned transform |
| * is equals to the sum of the target dimensions of all given transforms.</li> |
| * </ul> |
| * |
| * @param components the transforms to aggregate in a single transform, in the given order. |
| * @return the aggregation of all given transforms, or {@code null} if the given {@code components} array was empty. |
| * |
| * @see PassThroughTransform |
| * @see org.apache.sis.referencing.CRS#compound(CoordinateReferenceSystem...) |
| * @see org.apache.sis.geometry.Envelopes#compound(Envelope...) |
| * |
| * @since 0.6 |
| */ |
| public static MathTransform compound(final MathTransform... components) { |
| ArgumentChecks.ensureNonNull("components", components); |
| int sum = 0; |
| final int[] dimensions = new int[components.length]; |
| for (int i=0; i<components.length; i++) { |
| final MathTransform tr = components[i]; |
| ArgumentChecks.ensureNonNullElement("components", i, tr); |
| sum += (dimensions[i] = tr.getSourceDimensions()); |
| } |
| MathTransform compound = null; |
| int firstAffectedCoordinate = 0; |
| for (int i=0; i<components.length; i++) { |
| MathTransform tr = components[i]; |
| tr = passThrough(firstAffectedCoordinate, tr, sum - (firstAffectedCoordinate += dimensions[i])); |
| if (compound == null) { |
| compound = tr; |
| } else { |
| compound = concatenate(compound, tr); |
| } |
| } |
| assert isValid(getSteps(compound)) : compound; |
| return compound; |
| } |
| |
| /** |
| * Concatenates the two given transforms. The returned transform will implement |
| * {@link MathTransform1D} or {@link MathTransform2D} if the dimensions of the |
| * concatenated transform are equal to 1 or 2 respectively. |
| * |
| * @param tr1 the first math transform. |
| * @param tr2 the second math transform. |
| * @return the concatenated transform. |
| * @throws MismatchedDimensionException if the output dimension of the first transform |
| * does not match the input dimension of the second transform. |
| * |
| * @see DefaultMathTransformFactory#createConcatenatedTransform(MathTransform, MathTransform) |
| */ |
| public static MathTransform concatenate(final MathTransform tr1, final MathTransform tr2) |
| throws MismatchedDimensionException |
| { |
| ArgumentChecks.ensureNonNull("tr1", tr1); |
| ArgumentChecks.ensureNonNull("tr2", tr2); |
| final MathTransform tr; |
| try { |
| tr = ConcatenatedTransform.create(tr1, tr2, null); |
| } catch (FactoryException e) { |
| throw new IllegalArgumentException(e); // Should never happen actually. |
| } |
| assert isValid(getSteps(tr)) : tr; |
| return tr; |
| } |
| |
| /** |
| * Concatenates the given one-dimensional transforms. This is a convenience methods |
| * delegating to {@link #concatenate(MathTransform, MathTransform)} and casting the |
| * result to a {@link MathTransform1D} instance. |
| * |
| * @param tr1 the first math transform. |
| * @param tr2 the second math transform. |
| * @return the concatenated transform. |
| * @throws MismatchedDimensionException if the output dimension of the first transform |
| * does not match the input dimension of the second transform. |
| */ |
| public static MathTransform1D concatenate(MathTransform1D tr1, MathTransform1D tr2) |
| throws MismatchedDimensionException |
| { |
| return (MathTransform1D) concatenate((MathTransform) tr1, (MathTransform) tr2); |
| } |
| |
| /** |
| * Concatenates the given two-dimensional transforms. This is a convenience methods |
| * delegating to {@link #concatenate(MathTransform, MathTransform)} and casting the |
| * result to a {@link MathTransform2D} instance. |
| * |
| * @param tr1 the first math transform. |
| * @param tr2 the second math transform. |
| * @return the concatenated transform. |
| * @throws MismatchedDimensionException if the output dimension of the first transform |
| * does not match the input dimension of the second transform. |
| */ |
| public static MathTransform2D concatenate(MathTransform2D tr1, MathTransform2D tr2) |
| throws MismatchedDimensionException |
| { |
| return (MathTransform2D) concatenate((MathTransform) tr1, (MathTransform) tr2); |
| } |
| |
| /** |
| * Concatenates the three given transforms. This is a convenience methods doing its job |
| * as two consecutive concatenations. |
| * |
| * @param tr1 the first math transform. |
| * @param tr2 the second math transform. |
| * @param tr3 the third math transform. |
| * @return the concatenated transform. |
| * @throws MismatchedDimensionException if the output dimension of a transform |
| * does not match the input dimension of next transform. |
| */ |
| public static MathTransform concatenate(MathTransform tr1, MathTransform tr2, MathTransform tr3) |
| throws MismatchedDimensionException |
| { |
| ArgumentChecks.ensureNonNull("tr1", tr1); |
| ArgumentChecks.ensureNonNull("tr2", tr2); |
| ArgumentChecks.ensureNonNull("tr3", tr3); |
| return concatenate(concatenate(tr1, tr2), tr3); |
| } |
| |
| /** |
| * Concatenates the three given one-dimensional transforms. This is a convenience methods |
| * delegating to {@link #concatenate(MathTransform, MathTransform, MathTransform)} and |
| * casting the result to a {@link MathTransform1D} instance. |
| * |
| * @param tr1 the first math transform. |
| * @param tr2 the second math transform. |
| * @param tr3 the third math transform. |
| * @return the concatenated transform. |
| * @throws MismatchedDimensionException if the output dimension of a transform |
| * does not match the input dimension of next transform. |
| */ |
| public static MathTransform1D concatenate(MathTransform1D tr1, MathTransform1D tr2, MathTransform1D tr3) |
| throws MismatchedDimensionException |
| { |
| return (MathTransform1D) concatenate((MathTransform) tr1, (MathTransform) tr2, (MathTransform) tr3); |
| } |
| |
| /** |
| * Concatenates the three given two-dimensional transforms. This is a convenience methods |
| * delegating to {@link #concatenate(MathTransform, MathTransform, MathTransform)} and |
| * casting the result to a {@link MathTransform2D} instance. |
| * |
| * @param tr1 the first math transform. |
| * @param tr2 the second math transform. |
| * @param tr3 the third math transform. |
| * @return the concatenated transform. |
| * @throws MismatchedDimensionException if the output dimension of a transform |
| * does not match the input dimension of next transform. |
| */ |
| public static MathTransform2D concatenate(MathTransform2D tr1, MathTransform2D tr2, MathTransform2D tr3) |
| throws MismatchedDimensionException |
| { |
| return (MathTransform2D) concatenate((MathTransform) tr1, (MathTransform) tr2, (MathTransform) tr3); |
| } |
| |
| /** |
| * Makes sure that the given list does not contains two consecutive linear transforms |
| * (because their matrices should have been multiplied together). |
| * This is used for assertion purposes only. |
| */ |
| static boolean isValid(final List<MathTransform> steps) { |
| boolean wasLinear = false; |
| for (final MathTransform step : steps) { |
| if (step instanceof LinearTransform) { |
| if (wasLinear) return false; |
| wasLinear = true; |
| } else { |
| wasLinear = false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns all single components of the given (potentially concatenated) transform. |
| * This method makes the following choice: |
| * |
| * <ul> |
| * <li>If {@code transform} is {@code null}, returns an empty list.</li> |
| * <li>Otherwise if {@code transform} is the result of a call to a {@code concatenate(…)} method, |
| * returns all components. All nested concatenated transforms (if any) will be flattened.</li> |
| * <li>Otherwise returns the given transform in a list of size 1.</li> |
| * </ul> |
| * |
| * @param transform the transform for which to get the components, or {@code null}. |
| * @return all single math transforms performed by this concatenated transform. |
| */ |
| public static List<MathTransform> getSteps(final MathTransform transform) { |
| if (transform != null) { |
| if (transform instanceof ConcatenatedTransform) { |
| return ((ConcatenatedTransform) transform).getSteps(); |
| } else { |
| return Collections.singletonList(transform); |
| } |
| } else { |
| return Collections.emptyList(); |
| } |
| } |
| |
| /** |
| * If the given transform is linear, returns its coefficients as a matrix. |
| * More specifically: |
| * |
| * <ul> |
| * <li>If the given transform is an instance of {@link LinearTransform}, |
| * returns {@link LinearTransform#getMatrix()}.</li> |
| * <li>Otherwise if the given transform is an instance of {@link AffineTransform}, |
| * returns its coefficients in a {@link org.apache.sis.referencing.operation.matrix.Matrix3} instance.</li> |
| * <li>Otherwise returns {@code null}.</li> |
| * </ul> |
| * |
| * @param transform the transform for which to get the matrix, or {@code null}. |
| * @return the matrix of the given transform, or {@code null} if none. |
| * |
| * @see #linear(Matrix) |
| * @see LinearTransform#getMatrix() |
| */ |
| public static Matrix getMatrix(final MathTransform transform) { |
| if (transform instanceof LinearTransform) { |
| return ((LinearTransform) transform).getMatrix(); |
| } |
| if (transform instanceof AffineTransform) { |
| return AffineTransforms2D.toMatrix((AffineTransform) transform); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the coefficients of an affine transform in the vicinity of the given position. |
| * If the given transform is linear, then this method produces a result identical to {@link #getMatrix(MathTransform)}. |
| * Otherwise the returned matrix can be used for {@linkplain #linear(Matrix) building a linear transform} which can be |
| * used as an approximation of the given transform for short distances around the given position. |
| * |
| * @param transform the transform to approximate by an affine transform. |
| * @param position position in source CRS around which to get the coefficients of an affine transform approximation. |
| * @return the matrix of the given transform around the given position. |
| * @throws TransformException if an error occurred while transforming the given position or computing the derivative at |
| * that position. |
| * |
| * @since 1.0 |
| * |
| * @see #linear(MathTransform, DirectPosition) |
| */ |
| public static Matrix getMatrix(final MathTransform transform, final DirectPosition position) throws TransformException { |
| ArgumentChecks.ensureNonNull("transform", transform); |
| final int srcDim = transform.getSourceDimensions(); |
| ArgumentChecks.ensureDimensionMatches("position", srcDim, position); // Null position is okay for now. |
| final Matrix affine = getMatrix(transform); |
| if (affine != null) { |
| return affine; |
| // We accept null position here for consistency with MathTransform.derivative(DirectPosition). |
| } |
| ArgumentChecks.ensureNonNull("position", position); |
| final int tgtDim = transform.getTargetDimensions(); |
| double[] pts = new double[Math.max(srcDim + 1, tgtDim)]; |
| for (int i=0; i<srcDim; i++) { |
| pts[i] = position.getOrdinate(i); |
| } |
| final Matrix d = derivativeAndTransform(transform, pts, 0, pts, 0); |
| final MatrixSIS a = Matrices.createZero(tgtDim + 1, srcDim + 1); |
| for (int j=0; j<tgtDim; j++) { |
| for (int i=0; i<srcDim; i++) { |
| a.setElement(j, i, d.getElement(j, i)); |
| } |
| a.setElement(j, srcDim, pts[j]); |
| pts[j] = -position.getOrdinate(j); // To be used by a.translate(pts) later. |
| } |
| a.setElement(tgtDim, srcDim, 1); |
| /* |
| * At this point, the translation column in the matrix is set as if the coordinate system origin |
| * was at the given position. We want to keep the original coordinate system origin. We do that |
| * be applying a translation in the opposite direction before the affine transform. Translation |
| * terms were opportunistically set in the previous loop. |
| */ |
| pts = ArraysExt.resize(pts, srcDim + 1); |
| pts[srcDim] = 1; |
| a.translate(pts); |
| return a; |
| } |
| |
| /** |
| * A buckle method for calculating derivative and coordinate transformation in a single step. |
| * The transform result is stored in the given destination array, and the derivative matrix |
| * is returned. Invoking this method is equivalent to the following code, except that it may |
| * execute faster with some {@code MathTransform} implementations: |
| * |
| * {@preformat java |
| * DirectPosition ptSrc = ...; |
| * DirectPosition ptDst = ...; |
| * Matrix matrixDst = derivative(ptSrc); |
| * ptDst = transform(ptSrc, ptDst); |
| * } |
| * |
| * @param transform the transform to use. |
| * @param srcPts the array containing the source coordinate. |
| * @param srcOff the offset to the point to be transformed in the source array. |
| * @param dstPts the array into which the transformed coordinate is returned. |
| * @param dstOff the offset to the location of the transformed point that is stored in the destination array. |
| * @return the matrix of the transform derivative at the given source position. |
| * @throws TransformException if the point can't be transformed or if a problem occurred |
| * while calculating the derivative. |
| */ |
| public static Matrix derivativeAndTransform(final MathTransform transform, |
| final double[] srcPts, final int srcOff, |
| final double[] dstPts, final int dstOff) |
| throws TransformException |
| { |
| if (transform instanceof AbstractMathTransform) { |
| return ((AbstractMathTransform) transform).transform(srcPts, srcOff, dstPts, dstOff, true); |
| } |
| // Must be calculated before to transform the coordinate. |
| final Matrix derivative = transform.derivative( |
| new DirectPositionView.Double(srcPts, srcOff, transform.getSourceDimensions())); |
| if (dstPts != null) { |
| transform.transform(srcPts, srcOff, dstPts, dstOff, 1); |
| } |
| return derivative; |
| } |
| } |