001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
006: * (C) 2001, Institut de Recherche pour le Développement
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: */
018: package org.geotools.referencing.operation.transform;
019:
020: // J2SE dependencies
021: import java.awt.geom.AffineTransform;
022: import java.awt.geom.Point2D;
023: import java.io.Serializable;
024:
025: // OpenGIS dependencies
026: import org.opengis.parameter.ParameterValueGroup;
027: import org.opengis.referencing.operation.MathTransform;
028: import org.opengis.referencing.operation.MathTransform1D;
029: import org.opengis.referencing.operation.MathTransform2D;
030: import org.opengis.referencing.operation.Matrix;
031: import org.opengis.referencing.operation.NoninvertibleTransformException;
032: import org.opengis.referencing.operation.TransformException;
033: import org.opengis.geometry.DirectPosition;
034:
035: // Geotools dependencies
036: import org.geotools.geometry.GeneralDirectPosition;
037: import org.geotools.referencing.operation.matrix.XMatrix;
038: import org.geotools.referencing.operation.matrix.Matrix3;
039: import org.geotools.referencing.operation.matrix.GeneralMatrix;
040: import org.geotools.referencing.operation.LinearTransform;
041: import org.geotools.referencing.wkt.Formatter;
042: import org.geotools.resources.Utilities;
043: import org.geotools.resources.i18n.Errors;
044: import org.geotools.resources.i18n.ErrorKeys;
045:
046: /**
047: * Base class for concatenated transform. Concatenated transforms are
048: * serializable if all their step transforms are serializables.
049: *
050: * @since 2.0
051: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/ConcatenatedTransform.java $
052: * @version $Id: ConcatenatedTransform.java 24925 2007-03-27 20:12:08Z jgarnett $
053: * @author Martin Desruisseaux
054: */
055: public class ConcatenatedTransform extends AbstractMathTransform
056: implements Serializable {
057: /**
058: * Serial number for interoperability with different versions.
059: */
060: private static final long serialVersionUID = 5772066656987558634L;
061:
062: /**
063: * Small number for floating point comparaisons.
064: */
065: private static final double EPSILON = 1E-10;
066:
067: /**
068: * The first math transform.
069: */
070: public final MathTransform transform1;
071:
072: /**
073: * The second math transform.
074: */
075: public final MathTransform transform2;
076:
077: /**
078: * The inverse transform. This field will be computed only when needed.
079: * But it is serialized in order to avoid rounding error if the inverse
080: * transform is serialized instead of the original one.
081: */
082: private ConcatenatedTransform inverse;
083:
084: /**
085: * Constructs a concatenated transform. This constructor is for subclasses only. To
086: * create a concatenated transform, use the factory method {@link #create} instead.
087: *
088: * @param transform1 The first math transform.
089: * @param transform2 The second math transform.
090: */
091: protected ConcatenatedTransform(final MathTransform transform1,
092: final MathTransform transform2) {
093: this .transform1 = transform1;
094: this .transform2 = transform2;
095: if (!isValid()) {
096: throw new IllegalArgumentException(Errors.format(
097: ErrorKeys.CANT_CONCATENATE_TRANSFORMS_$2,
098: getName(transform1), getName(transform2)));
099: }
100: }
101:
102: /**
103: * Returns the underlying matrix for the specified transform,
104: * or {@code null} if the matrix is unavailable.
105: */
106: private static XMatrix getMatrix(final MathTransform transform) {
107: if (transform instanceof LinearTransform) {
108: return toXMatrix(((LinearTransform) transform).getMatrix());
109: }
110: if (transform instanceof AffineTransform) {
111: return new Matrix3((AffineTransform) transform);
112: }
113: return null;
114: }
115:
116: /**
117: * Tests if one math transform is the inverse of the other. This implementation
118: * can't detect every case. It just detect the case when {@code tr2} is an
119: * instance of {@link AbstractMathTransform.Inverse}.
120: *
121: * @todo We could make this test more general (just compare with tr2.inverse(),
122: * no matter if it is an instance of AbstractMathTransform.Inverse or not,
123: * and catch the exception if one is thrown). Would it be too expensive to
124: * create inconditionnaly the inverse transform?
125: */
126: private static boolean areInverse(final MathTransform tr1,
127: final MathTransform tr2) {
128: if (tr2 instanceof AbstractMathTransform.Inverse) {
129: return tr1.equals(((AbstractMathTransform.Inverse) tr2)
130: .inverse());
131: }
132: return false;
133: }
134:
135: /**
136: * Constructs a concatenated transform. This factory method checks for step transforms
137: * dimension. The returned transform will implements {@link MathTransform2D} if source and
138: * target dimensions are equal to 2. Likewise, it will implements {@link MathTransform1D}
139: * if source and target dimensions are equal to 1. {@link MathTransform} implementations
140: * are available in two version: direct and non-direct. The "non-direct" version use an
141: * intermediate buffer when performing transformations; they are slower and consume more
142: * memory. They are used only as a fallback when a "direct" version can't be created.
143: *
144: * @param tr1 The first math transform.
145: * @param tr2 The second math transform.
146: * @return The concatenated transform.
147: *
148: * @todo We could add one more optimisation: if one transform is a matrix and the
149: * other transform is a PassThroughTransform, and if the matrix as 0 elements
150: * for all rows matching the PassThrough sub-transform, then we can get ride
151: * of the whole PassThroughTransform object.
152: */
153: public static MathTransform create(MathTransform tr1,
154: MathTransform tr2) {
155: final int dim1 = tr1.getTargetDimensions();
156: final int dim2 = tr2.getSourceDimensions();
157: if (dim1 != dim2) {
158: throw new IllegalArgumentException(Errors.format(
159: ErrorKeys.CANT_CONCATENATE_TRANSFORMS_$2,
160: getName(tr1), getName(tr2))
161: + ' '
162: + Errors.format(ErrorKeys.MISMATCHED_DIMENSION_$2,
163: new Integer(dim1), new Integer(dim2)));
164: }
165: if (tr1.isIdentity())
166: return tr2;
167: if (tr2.isIdentity())
168: return tr1;
169:
170: // If both transforms use matrix, then we can create
171: // a single transform using the concatenated matrix.
172: final XMatrix matrix1 = getMatrix(tr1);
173: if (matrix1 != null) {
174: final XMatrix matrix2 = getMatrix(tr2);
175: if (matrix2 != null) {
176: // Compute "matrix = matrix2 * matrix1". Reuse an existing matrix object
177: // if possible, which is always the case when both matrix are square.
178: final int numRow = matrix2.getNumRow();
179: final int numCol = matrix1.getNumCol();
180: final XMatrix matrix;
181: if (numCol == matrix2.getNumCol()) {
182: matrix = matrix2;
183: matrix2.multiply(matrix1);
184: } else {
185: final GeneralMatrix m = new GeneralMatrix(numRow,
186: numCol);
187: m.mul(toGMatrix(matrix2), toGMatrix(matrix1));
188: matrix = m;
189: }
190: if (matrix.isIdentity(EPSILON)) {
191: matrix.setIdentity();
192: }
193: // May not be really affine, but work anyway...
194: // This call will detect and optimize the special
195: // case where an 'AffineTransform' can be used.
196: return ProjectiveTransform.create(matrix);
197: }
198: }
199:
200: // If one transform is the inverse of the
201: // other, returns the identity transform.
202: if (areInverse(tr1, tr2) || areInverse(tr2, tr1)) {
203: assert tr1.getSourceDimensions() == tr2
204: .getTargetDimensions();
205: assert tr1.getTargetDimensions() == tr2
206: .getSourceDimensions();
207: return IdentityTransform.create(tr1.getSourceDimensions());
208: }
209:
210: // If one or both math transform are instance of ConcatenatedTransform,
211: // then maybe it is possible to efficiently concatenate tr1 or tr2 with
212: // one of step transforms. Try that...
213: if (tr1 instanceof ConcatenatedTransform) {
214: final ConcatenatedTransform ctr = (ConcatenatedTransform) tr1;
215: tr1 = ctr.transform1;
216: tr2 = create(ctr.transform2, tr2);
217: }
218: if (tr2 instanceof ConcatenatedTransform) {
219: final ConcatenatedTransform ctr = (ConcatenatedTransform) tr2;
220: tr1 = create(tr1, ctr.transform1);
221: tr2 = ctr.transform2;
222: }
223: // Tests again, because one of the 'create' methods
224: // above may have returned an identity transform.
225: if (tr1.isIdentity())
226: return tr2;
227: if (tr2.isIdentity())
228: return tr1;
229:
230: // Before to create a general ConcatenatedTransform object, give a
231: // chance to AbstractMathTransform to returns an optimized object.
232: if (tr1 instanceof AbstractMathTransform) {
233: final MathTransform optimized = ((AbstractMathTransform) tr1)
234: .concatenate(tr2, false);
235: if (optimized != null) {
236: return optimized;
237: }
238: }
239: if (tr2 instanceof AbstractMathTransform) {
240: final MathTransform optimized = ((AbstractMathTransform) tr2)
241: .concatenate(tr1, true);
242: if (optimized != null) {
243: return optimized;
244: }
245: }
246: // Can't avoid the creation of a ConcatenatedTransform object.
247: // Check for the type to create (1D, 2D, general case...)
248: return createConcatenatedTransform(tr1, tr2);
249: }
250:
251: /**
252: * Continue the construction started by {@link #create}. The construction step is available
253: * separatly for testing purpose (in a JUnit test), and for {@link #inverse()} implementation.
254: */
255: static ConcatenatedTransform createConcatenatedTransform(
256: final MathTransform tr1, final MathTransform tr2) {
257: final int dimSource = tr1.getSourceDimensions();
258: final int dimTarget = tr2.getTargetDimensions();
259: //
260: // Check if the result need to be a MathTransform1D.
261: //
262: if (dimSource == 1 && dimTarget == 1) {
263: if (tr1 instanceof MathTransform1D
264: && tr2 instanceof MathTransform1D) {
265: return new ConcatenatedTransformDirect1D(
266: (MathTransform1D) tr1, (MathTransform1D) tr2);
267: } else {
268: return new ConcatenatedTransform1D(tr1, tr2);
269: }
270: } else
271: //
272: // Check if the result need to be a MathTransform2D.
273: //
274: if (dimSource == 2 && dimTarget == 2) {
275: if (tr1 instanceof MathTransform2D
276: && tr2 instanceof MathTransform2D) {
277: return new ConcatenatedTransformDirect2D(
278: (MathTransform2D) tr1, (MathTransform2D) tr2);
279: } else {
280: return new ConcatenatedTransform2D(tr1, tr2);
281: }
282: } else
283: //
284: // Check for the general case.
285: //
286: if (dimSource == tr1.getTargetDimensions()
287: && tr2.getSourceDimensions() == dimTarget) {
288: return new ConcatenatedTransformDirect(tr1, tr2);
289: } else {
290: return new ConcatenatedTransform(tr1, tr2);
291: }
292: }
293:
294: /**
295: * Returns a name for the specified math transform.
296: */
297: private static final String getName(final MathTransform transform) {
298: if (transform instanceof AbstractMathTransform) {
299: ParameterValueGroup params = ((AbstractMathTransform) transform)
300: .getParameterValues();
301: if (params != null) {
302: String name = params.getDescriptor().getName()
303: .getCode();
304: if (name != null && (name = name.trim()).length() != 0) {
305: return name;
306: }
307: }
308: }
309: return Utilities.getShortClassName(transform);
310: }
311:
312: /**
313: * Check if transforms are compatibles. The default
314: * implementation check if transfert dimension match.
315: */
316: boolean isValid() {
317: return transform1.getTargetDimensions() == transform2
318: .getSourceDimensions();
319: }
320:
321: /**
322: * Gets the dimension of input points.
323: */
324: public final int getSourceDimensions() {
325: return transform1.getSourceDimensions();
326: }
327:
328: /**
329: * Gets the dimension of output points.
330: */
331: public final int getTargetDimensions() {
332: return transform2.getTargetDimensions();
333: }
334:
335: /**
336: * Transforms the specified {@code ptSrc} and stores the result in {@code ptDst}.
337: */
338: public DirectPosition transform(final DirectPosition ptSrc,
339: DirectPosition ptDst) throws TransformException {
340: assert isValid();
341: // Note: If we know that the transfert dimension is the same than source
342: // and target dimension, then we don't need to use an intermediate
343: // point. This optimization is done in ConcatenatedTransformDirect.
344: return transform2.transform(transform1.transform(ptSrc, null),
345: ptDst);
346: }
347:
348: /**
349: * Transforms a list of coordinate point ordinal values.
350: */
351: public void transform(final double[] srcPts, final int srcOff,
352: final double[] dstPts, final int dstOff, final int numPts)
353: throws TransformException {
354: assert isValid();
355: // Note: If we know that the transfert dimension is the same than source
356: // and target dimension, then we don't need to use an intermediate
357: // buffer. This optimization is done in ConcatenatedTransformDirect.
358: final double[] tmp = new double[numPts
359: * transform1.getTargetDimensions()];
360: transform1.transform(srcPts, srcOff, tmp, 0, numPts);
361: transform2.transform(tmp, 0, dstPts, dstOff, numPts);
362: }
363:
364: /**
365: * Transforms a list of coordinate point ordinal values.
366: */
367: public void transform(final float[] srcPts, final int srcOff,
368: final float[] dstPts, final int dstOff, final int numPts)
369: throws TransformException {
370: assert isValid();
371: // Note: If we know that the transfert dimension is the same than source
372: // and target dimension, then we don't need to use an intermediate
373: // buffer. This optimization is done in ConcatenatedTransformDirect.
374: final float[] tmp = new float[numPts
375: * transform1.getTargetDimensions()];
376: transform1.transform(srcPts, srcOff, tmp, 0, numPts);
377: transform2.transform(tmp, 0, dstPts, dstOff, numPts);
378: }
379:
380: /**
381: * Creates the inverse transform of this object.
382: */
383: public synchronized final MathTransform inverse()
384: throws NoninvertibleTransformException {
385: assert isValid();
386: if (inverse == null) {
387: inverse = createConcatenatedTransform(transform2.inverse(),
388: transform1.inverse());
389: inverse.inverse = this ;
390: }
391: return inverse;
392: }
393:
394: /**
395: * Gets the derivative of this transform at a point. This method delegates to the
396: * {@link #derivative(DirectPosition)} method because the transformation steps
397: * {@link #transform1} and {@link #transform2} may not be instances of
398: * {@link MathTransform2D}.
399: *
400: * @param point The coordinate point where to evaluate the derivative.
401: * @return The derivative at the specified point as a 2×2 matrix.
402: * @throws TransformException if the derivative can't be evaluated at the specified point.
403: */
404: public Matrix derivative(final Point2D point)
405: throws TransformException {
406: return derivative(new GeneralDirectPosition(point));
407: }
408:
409: /**
410: * Gets the derivative of this transform at a point.
411: *
412: * @param point The coordinate point where to evaluate the derivative.
413: * @return The derivative at the specified point (never {@code null}).
414: * @throws TransformException if the derivative can't be evaluated at the specified point.
415: */
416: public Matrix derivative(final DirectPosition point)
417: throws TransformException {
418: final Matrix matrix1 = transform1.derivative(point);
419: final Matrix matrix2 = transform2.derivative(transform1
420: .transform(point, null));
421: // Compute "matrix = matrix2 * matrix1". Reuse an existing matrix object
422: // if possible, which is always the case when both matrix are square.
423: final int numRow = matrix2.getNumRow();
424: final int numCol = matrix1.getNumCol();
425: final XMatrix matrix;
426: if (numCol == matrix2.getNumCol()) {
427: matrix = toXMatrix(matrix2);
428: matrix.multiply(matrix1);
429: } else {
430: final GeneralMatrix m = new GeneralMatrix(numRow, numCol);
431: m.mul(toGMatrix(matrix2), toGMatrix(matrix1));
432: matrix = m;
433: }
434: return matrix;
435: }
436:
437: /**
438: * Tests whether this transform does not move any points.
439: * Default implementation check if the two transforms are
440: * identity.
441: */
442: public final boolean isIdentity() {
443: return transform1.isIdentity() && transform2.isIdentity();
444: }
445:
446: /**
447: * Returns a hash value for this transform.
448: */
449: public final int hashCode() {
450: return transform1.hashCode() + 37 * transform2.hashCode();
451: }
452:
453: /**
454: * Compares the specified object with
455: * this math transform for equality.
456: */
457: public final boolean equals(final Object object) {
458: if (object == this ) {
459: // Slight optimization
460: return true;
461: }
462: if (super .equals(object)) {
463: final ConcatenatedTransform that = (ConcatenatedTransform) object;
464: return Utilities.equals(this .transform1, that.transform1)
465: && Utilities.equals(this .transform2,
466: that.transform2);
467: }
468: return false;
469: }
470:
471: /**
472: * Format the inner part of a
473: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
474: * Known Text</cite> (WKT)</A> element.
475: *
476: * @param formatter The formatter to use.
477: * @return The WKT element name.
478: */
479: protected String formatWKT(final Formatter formatter) {
480: addWKT(formatter, transform1);
481: addWKT(formatter, transform2);
482: return "CONCAT_MT";
483: }
484:
485: /**
486: * Append to a string buffer the WKT for the specified math transform.
487: */
488: private static void addWKT(final Formatter formatter,
489: final MathTransform transform) {
490: if (transform instanceof ConcatenatedTransform) {
491: final ConcatenatedTransform concat = (ConcatenatedTransform) transform;
492: addWKT(formatter, concat.transform1);
493: addWKT(formatter, concat.transform2);
494: } else {
495: formatter.append(transform);
496: }
497: }
498: }
|