001    /*
002     *    GeoAPI - Java interfaces for OGC/ISO standards
003     *    http://www.geoapi.org
004     *
005     *    This file is hereby placed into the Public Domain.
006     *    This means anyone is free to do whatever they wish with this file.
007     */
008    package org.opengis.example.referencing;
009    
010    import javax.vecmath.GMatrix;
011    
012    import org.opengis.metadata.citation.Citation;
013    import org.opengis.geometry.DirectPosition;
014    import org.opengis.geometry.MismatchedDimensionException;
015    import org.opengis.referencing.crs.CoordinateReferenceSystem;
016    import org.opengis.referencing.operation.Matrix;
017    import org.opengis.referencing.operation.MathTransform;
018    import org.opengis.referencing.operation.NoninvertibleTransformException;
019    
020    import org.opengis.example.geometry.SimpleDirectPosition;
021    
022    
023    /**
024     * A {@link MathTransform} which use a {@link Matrix} for transforming the coordinates.
025     * This transform is usually, but not necessarily, affine.
026     *
027     * <p><b>Constraints:</b></p>
028     * <ul>
029     *   <li>The {@linkplain Matrix#getNumCol() number of columns} in the matrix shall be equal
030     *       to the number of source dimensions + 1.</li>
031     *   <li>The {@linkplain Matrix#getNumRow() number of rows} in the matrix shall be equal
032     *       to the number of target dimensions + 1.</li>
033     * </ul>
034     *
035     * <b>Performance note:</b>
036     * This implementation is known to be slow. However the intend is to be pedagogic, not to be
037     * efficient. Performance enhancements are left to implementors (<i>Tip:</i> override all
038     * {@code transform} methods expecting array arguments).
039     *
040     * @author  Martin Desruisseaux (Geomatys)
041     * @version 3.1
042     * @since   3.1
043     *
044     * @see SimpleTransformFactory#createAffineTransform(Matrix)
045     */
046    public class ProjectiveTransform extends SimpleTransform {
047        /**
048         * For cross-version compatibility.
049         */
050        private static final long serialVersionUID = -6681647845536764717L;
051    
052        /**
053         * The matrix used for performing the coordinate conversions.
054         */
055        protected final SimpleMatrix matrix;
056    
057        /**
058         * The inverse of this transform, computed when first needed.
059         */
060        private transient ProjectiveTransform inverse;
061    
062        /**
063         * Creates a new operation for the given name, CRS and matrix.
064         *
065         * @param authority Organization responsible for definition of the name, or {@code null}.
066         * @param name      The name of the new CRS.
067         * @param sourceCRS The source CRS to be returned by {@link #getSourceCRS()}, or {@code null}.
068         * @param targetCRS The target CRS to be returned by {@link #getTargetCRS()}, or {@code null}.
069         * @param matrix    The matrix. See class javadoc for constraints on the matrix size.
070         */
071        public ProjectiveTransform(final Citation authority, final String name,
072                final CoordinateReferenceSystem sourceCRS,
073                final CoordinateReferenceSystem targetCRS,
074                final SimpleMatrix matrix)
075        {
076            super(authority, name, sourceCRS, targetCRS);
077            Objects.requireNonNull(matrix);
078            this.matrix = matrix;
079            if (sourceCRS != null && sourceCRS.getCoordinateSystem().getDimension() != matrix.getNumCol() - 1) {
080                throw new MismatchedDimensionException("Wrong number of source dimensions.");
081            }
082            if (targetCRS != null && targetCRS.getCoordinateSystem().getDimension() != matrix.getNumRow() - 1) {
083                throw new MismatchedDimensionException("Wrong number of target dimensions.");
084            }
085        }
086    
087        /**
088         * Gets the dimension of input points. This is equals to the
089         * {@linkplain Matrix#getNumCol() number of columns} in the matrix minus one.
090         */
091        @Override
092        public int getSourceDimensions() {
093            return matrix.getNumCol() - 1;
094        }
095    
096        /**
097         * Gets the dimension of target points. This is equals to the
098         * {@linkplain Matrix#getNumRow() number of rows} in the matrix minus one.
099         */
100        @Override
101        public int getTargetDimensions() {
102            return matrix.getNumRow() - 1;
103        }
104    
105        /**
106         * Transforms the specified {@code ptSrc}. First, this implementation computes the
107         * following matrix product:
108         *
109         * <blockquote><pre>
110         * ┌     ┐     ┌      ┐ ┌     ┐
111         * │ptDst│  =  │{@linkplain #matrix}│ │ptSrc│
112         * │  w  │     │      │ │  1  │
113         * └     ┘     └      ┘ └     ┘</pre></blockquote>
114         * <p>
115         * Then, the destination ordinate values are divided by <var>w</var>. Note that in the
116         * common case where the transform is affine, <var>w</var> = 1.
117         */
118        @Override
119        public DirectPosition transform(final DirectPosition ptSrc, DirectPosition ptDst) throws MismatchedDimensionException {
120            //
121            // Check arguments validity.
122            //
123            final int srcDim = matrix.getNumCol() - 1;
124            final int dstDim = matrix.getNumRow() - 1;
125            if (ptSrc.getDimension() != srcDim) {
126                throw new MismatchedDimensionException("Wrong number of source dimensions.");
127            }
128            if (ptDst != null) {
129                if (ptDst.getDimension() != dstDim) {
130                    throw new MismatchedDimensionException("Wrong number of target dimensions.");
131                }
132            } else {
133                ptDst = new SimpleDirectPosition(dstDim);
134            }
135            //
136            // Create two matrixes of 1 column, which will
137            // represent the source and target coordinates.
138            //
139            final GMatrix source = new GMatrix(srcDim+1, 1);
140            final GMatrix target = new GMatrix(dstDim+1, 1);
141            source.setElement(srcDim, 0, 1);
142            for (int j=0; j<srcDim; j++) {
143                source.setElement(j, 0, ptSrc.getOrdinate(j));
144            }
145            //
146            // Compute [target] = [matrix]*[source]
147            // as documented in the method javadoc.
148            //
149            target.mul(matrix, source);
150            final double w = target.getElement(dstDim, 0); // =1 if the transform is affine.
151            for (int j=0; j<dstDim; j++) {
152                ptDst.setOrdinate(j, target.getElement(j, 0) / w);
153            }
154            return ptDst;
155        }
156    
157        /**
158         * Gets the derivative of this transform. In the particular case of linear transforms,
159         * the derivative is the same at every points. Consequently the {@code point} argument
160         * is ignored.
161         *
162         * @param  point Ignored for a linear transform.
163         * @return The derivative (never {@code null}).
164         */
165        @Override
166        public Matrix derivative(final DirectPosition point) {
167            final int srcDim = matrix.getNumCol() - 1;
168            final int dstDim = matrix.getNumRow() - 1;
169            final SimpleMatrix derivative = new SimpleMatrix(dstDim, srcDim);
170            matrix.copySubMatrix(0, 0, dstDim, srcDim, 0, 0, derivative);
171            return derivative;
172        }
173    
174        /**
175         * Returns the inverse transform of this object. The default implementation
176         * {@linkplain SimpleMatrix#invert() invert} the {@linkplain #matrix} and
177         * build a new {@code ProjectiveTransform} from it.
178         */
179        @Override
180        public synchronized ProjectiveTransform inverse() throws NoninvertibleTransformException {
181            if (inverse == null) {
182                final SimpleMatrix invert = matrix.clone();
183                try {
184                    invert.invert();
185                } catch (RuntimeException e) { // SingularMatrixException & MismatchedSizeException
186                    throw new NoninvertibleTransformException("Can not invert \"" + code + '"', e);
187                }
188                inverse = new ProjectiveTransform(authority, "Inverse of " + code, targetCRS, sourceCRS, invert);
189                inverse.inverse = this;
190            }
191            return inverse;
192        }
193    
194        /**
195         * Tests whether this transform does not move any points.
196         * The default implementation delegates to {@link SimpleMatrix#isIdentity()}.
197         */
198        @Override
199        public boolean isIdentity() {
200            return matrix.isIdentity();
201        }
202    
203        /**
204         * {@inheritDoc}
205         */
206        @Override
207        public boolean equals(final Object object) {
208            if (super.equals(object)) {
209                return matrix.equals(((ProjectiveTransform) object).matrix);
210            }
211            return false;
212        }
213    }