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 }