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.io.Serializable;
022:
023: // OpenGIS dependencies
024: import org.opengis.referencing.operation.MathTransform;
025: import org.opengis.referencing.operation.Matrix;
026: import org.opengis.referencing.operation.NoninvertibleTransformException;
027: import org.opengis.referencing.operation.TransformException;
028: import org.opengis.geometry.DirectPosition;
029: import org.opengis.geometry.MismatchedDimensionException;
030:
031: // Geotools dependencies
032: import org.geotools.geometry.GeneralDirectPosition;
033: import org.geotools.referencing.operation.LinearTransform;
034: import org.geotools.referencing.operation.matrix.GeneralMatrix;
035: import org.geotools.referencing.wkt.Formatter;
036: import org.geotools.resources.Utilities;
037: import org.geotools.resources.i18n.ErrorKeys;
038: import org.geotools.resources.i18n.Errors;
039:
040: /**
041: * Transform which passes through a subset of ordinates to another transform.
042: * This allows transforms to operate on a subset of ordinates. For example,
043: * if you have (<var>latitude</var>,<var>longitude</var>,<var>height</var>)
044: * coordinates, then you may wish to convert the height values from feet to
045: * meters without affecting the latitude and longitude values.
046: *
047: * @since 2.0
048: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/PassThroughTransform.java $
049: * @version $Id: PassThroughTransform.java 24925 2007-03-27 20:12:08Z jgarnett $
050: * @author Martin Desruisseaux
051: *
052: * @see DimensionFilter
053: */
054: public class PassThroughTransform extends AbstractMathTransform
055: implements Serializable {
056: /**
057: * Serial number for interoperability with different versions.
058: */
059: private static final long serialVersionUID = -1673997634240223449L;
060:
061: /**
062: * Index of the first affected ordinate.
063: */
064: protected final int firstAffectedOrdinate;
065:
066: /**
067: * Number of unaffected ordinates after the affected ones.
068: * Always 0 when used through the strict OpenGIS API.
069: */
070: protected final int numTrailingOrdinates;
071:
072: /**
073: * The sub transform.
074: *
075: * @see #getSubTransform
076: */
077: protected final MathTransform subTransform;
078:
079: /**
080: * The inverse transform. This field will be computed only when needed.
081: * But it is serialized in order to avoid rounding error.
082: */
083: private PassThroughTransform inverse;
084:
085: /**
086: * Create a pass through transform.
087: *
088: * @param firstAffectedOrdinate Index of the first affected ordinate.
089: * @param subTransform The sub transform.
090: * @param numTrailingOrdinates Number of trailing ordinates to pass through.
091: * Affected ordinates will range from {@code firstAffectedOrdinate}
092: * inclusive to {@code dimTarget-numTrailingOrdinates} exclusive.
093: */
094: protected PassThroughTransform(final int firstAffectedOrdinate,
095: final MathTransform subTransform,
096: final int numTrailingOrdinates) {
097: if (firstAffectedOrdinate < 0) {
098: throw new IllegalArgumentException(Errors.format(
099: ErrorKeys.ILLEGAL_ARGUMENT_$2,
100: "firstAffectedOrdinate", new Integer(
101: firstAffectedOrdinate)));
102: }
103: if (numTrailingOrdinates < 0) {
104: throw new IllegalArgumentException(Errors.format(
105: ErrorKeys.ILLEGAL_ARGUMENT_$2,
106: "numTrailingOrdinates", new Integer(
107: numTrailingOrdinates)));
108: }
109: if (subTransform instanceof PassThroughTransform) {
110: final PassThroughTransform passThrough = (PassThroughTransform) subTransform;
111: this .firstAffectedOrdinate = passThrough.firstAffectedOrdinate
112: + firstAffectedOrdinate;
113: this .numTrailingOrdinates = passThrough.numTrailingOrdinates
114: + numTrailingOrdinates;
115: this .subTransform = passThrough.subTransform;
116: } else {
117: this .firstAffectedOrdinate = firstAffectedOrdinate;
118: this .numTrailingOrdinates = numTrailingOrdinates;
119: this .subTransform = subTransform;
120: }
121: }
122:
123: /**
124: * Creates a transform which passes through a subset of ordinates to another transform.
125: * This allows transforms to operate on a subset of ordinates. For example, if you have
126: * (<var>latitidue</var>,<var>longitude</var>,<var>height</var>) coordinates, then you
127: * may wish to convert the height values from feet to meters without affecting the
128: * latitude and longitude values.
129: *
130: * @param firstAffectedOrdinate Index of the first affected ordinate.
131: * @param subTransform The sub transform.
132: * @param numTrailingOrdinates Number of trailing ordinates to pass through.
133: * Affected ordinates will range from {@code firstAffectedOrdinate}
134: * inclusive to {@code dimTarget-numTrailingOrdinates} exclusive.
135: * @return A pass through transform with the following dimensions:<br>
136: * <pre>
137: * Source: firstAffectedOrdinate + subTransform.getSourceDimensions() + numTrailingOrdinates
138: * Target: firstAffectedOrdinate + subTransform.getTargetDimensions() + numTrailingOrdinates</pre>
139: */
140: public static MathTransform create(final int firstAffectedOrdinate,
141: final MathTransform subTransform,
142: final int numTrailingOrdinates) {
143: if (firstAffectedOrdinate < 0) {
144: throw new IllegalArgumentException(Errors.format(
145: ErrorKeys.ILLEGAL_ARGUMENT_$2,
146: "firstAffectedOrdinate", new Integer(
147: firstAffectedOrdinate)));
148: }
149: if (numTrailingOrdinates < 0) {
150: throw new IllegalArgumentException(Errors.format(
151: ErrorKeys.ILLEGAL_ARGUMENT_$2,
152: "numTrailingOrdinates", new Integer(
153: numTrailingOrdinates)));
154: }
155: if (firstAffectedOrdinate == 0 && numTrailingOrdinates == 0) {
156: return subTransform;
157: }
158: /*
159: * Optimize the "Identity transform" case.
160: */
161: if (subTransform.isIdentity()) {
162: final int dimension = subTransform.getSourceDimensions();
163: if (dimension == subTransform.getTargetDimensions()) {
164: return IdentityTransform.create(firstAffectedOrdinate
165: + dimension + numTrailingOrdinates);
166: }
167: }
168: /*
169: * Special case for transformation backed by a matrix. Is is possible to use a
170: * new matrix for such transform, instead of wrapping the sub-transform into a
171: * PassThroughTransform object. It is faster and easier to concatenate.
172: */
173: if (subTransform instanceof LinearTransform) {
174: GeneralMatrix matrix = toGMatrix(((LinearTransform) subTransform)
175: .getMatrix());
176: matrix = PassThroughTransform.expand(matrix,
177: firstAffectedOrdinate, numTrailingOrdinates, 1);
178: return ProjectiveTransform.create(matrix);
179: }
180: /*
181: * Constructs the general PassThroughTransform object. An optimisation
182: * for the "Pass through case" is done right in the constructor.
183: */
184: return new PassThroughTransform(firstAffectedOrdinate,
185: subTransform, numTrailingOrdinates);
186: }
187:
188: /**
189: * Returns the sub transform.
190: *
191: * @since 2.2
192: */
193: public MathTransform getSubTransform() {
194: return subTransform;
195: }
196:
197: /**
198: * Ordered sequence of positive integers defining the positions in a coordinate
199: * tuple of the coordinates affected by this pass-through transform. The returned
200: * index are for source coordinates.
201: *
202: * @return The modified coordinates.
203: */
204: public int[] getModifiedCoordinates() {
205: final int[] index = new int[subTransform.getSourceDimensions()];
206: for (int i = 0; i < index.length; i++) {
207: index[i] = i + firstAffectedOrdinate;
208: }
209: return index;
210: }
211:
212: /**
213: * Gets the dimension of input points.
214: */
215: public int getSourceDimensions() {
216: return firstAffectedOrdinate
217: + subTransform.getSourceDimensions()
218: + numTrailingOrdinates;
219: }
220:
221: /**
222: * Gets the dimension of output points.
223: */
224: public int getTargetDimensions() {
225: return firstAffectedOrdinate
226: + subTransform.getTargetDimensions()
227: + numTrailingOrdinates;
228: }
229:
230: /**
231: * Tests whether this transform does not move any points.
232: */
233: public boolean isIdentity() {
234: return subTransform.isIdentity();
235: }
236:
237: /**
238: * Transforms a list of coordinate point ordinal values.
239: */
240: public void transform(final float[] srcPts, int srcOff,
241: final float[] dstPts, int dstOff, int numPts)
242: throws TransformException {
243: final int subDimSource = subTransform.getSourceDimensions();
244: final int subDimTarget = subTransform.getTargetDimensions();
245: int srcStep = numTrailingOrdinates;
246: int dstStep = numTrailingOrdinates;
247: if (srcPts == dstPts && srcOff < dstOff) {
248: final int dimSource = getSourceDimensions();
249: final int dimTarget = getTargetDimensions();
250: srcOff += numPts * dimSource;
251: dstOff += numPts * dimTarget;
252: srcStep -= 2 * dimSource;
253: dstStep -= 2 * dimTarget;
254: }
255: while (--numPts >= 0) {
256: System.arraycopy(srcPts, srcOff, dstPts, dstOff,
257: firstAffectedOrdinate);
258: subTransform.transform(srcPts,
259: srcOff += firstAffectedOrdinate, dstPts,
260: dstOff += firstAffectedOrdinate, 1);
261: System.arraycopy(srcPts, srcOff += subDimSource, dstPts,
262: dstOff += subDimTarget, numTrailingOrdinates);
263: srcOff += srcStep;
264: dstOff += dstStep;
265: }
266: }
267:
268: /**
269: * Transforms a list of coordinate point ordinal values.
270: */
271: public void transform(final double[] srcPts, int srcOff,
272: final double[] dstPts, int dstOff, int numPts)
273: throws TransformException {
274: final int subDimSource = subTransform.getSourceDimensions();
275: final int subDimTarget = subTransform.getTargetDimensions();
276: int srcStep = numTrailingOrdinates;
277: int dstStep = numTrailingOrdinates;
278: if (srcPts == dstPts && srcOff < dstOff) {
279: final int dimSource = getSourceDimensions();
280: final int dimTarget = getTargetDimensions();
281: srcOff += numPts * dimSource;
282: dstOff += numPts * dimTarget;
283: srcStep -= 2 * dimSource;
284: dstStep -= 2 * dimTarget;
285: }
286: while (--numPts >= 0) {
287: System.arraycopy(srcPts, srcOff, dstPts, dstOff,
288: firstAffectedOrdinate);
289: subTransform.transform(srcPts,
290: srcOff += firstAffectedOrdinate, dstPts,
291: dstOff += firstAffectedOrdinate, 1);
292: System.arraycopy(srcPts, srcOff += subDimSource, dstPts,
293: dstOff += subDimTarget, numTrailingOrdinates);
294: srcOff += srcStep;
295: dstOff += dstStep;
296: }
297: }
298:
299: /**
300: * Gets the derivative of this transform at a point.
301: */
302: public Matrix derivative(final DirectPosition point)
303: throws TransformException {
304: final int nSkipped = firstAffectedOrdinate
305: + numTrailingOrdinates;
306: final int transDim = subTransform.getSourceDimensions();
307: final int pointDim = point.getDimension();
308: if (pointDim != transDim + nSkipped) {
309: throw new MismatchedDimensionException(Errors.format(
310: ErrorKeys.MISMATCHED_DIMENSION_$3, "point",
311: new Integer(pointDim), new Integer(transDim
312: + nSkipped)));
313: }
314: final GeneralDirectPosition subPoint = new GeneralDirectPosition(
315: transDim);
316: for (int i = 0; i < transDim; i++) {
317: subPoint.ordinates[i] = point.getOrdinate(i
318: + firstAffectedOrdinate);
319: }
320: return expand(toGMatrix(subTransform.derivative(subPoint)),
321: firstAffectedOrdinate, numTrailingOrdinates, 0);
322: }
323:
324: /**
325: * Creates a pass through transform from a matrix. This method is invoked when the
326: * sub-transform can be express as a matrix. It is also invoked for computing the
327: * matrix returned by {@link #derivative}.
328: *
329: * @param subMatrix The sub-transform as a matrix.
330: * @param firstAffectedOrdinate Index of the first affected ordinate.
331: * @param numTrailingOrdinates Number of trailing ordinates to pass through.
332: * @param affine 0 if the matrix do not contains translation terms, or 1 if
333: * the matrix is an affine transform with translation terms.
334: */
335: private static GeneralMatrix expand(final GeneralMatrix subMatrix,
336: final int firstAffectedOrdinate,
337: final int numTrailingOrdinates, final int affine) {
338: final int nSkipped = firstAffectedOrdinate
339: + numTrailingOrdinates;
340: final int numRow = subMatrix.getNumRow() - affine;
341: final int numCol = subMatrix.getNumCol() - affine;
342: final GeneralMatrix matrix = new GeneralMatrix(numRow
343: + nSkipped + affine, numCol + nSkipped + affine);
344: matrix.setZero();
345:
346: // Set UL part to 1: [ 1 0 ]
347: // [ 0 1 ]
348: // [ ]
349: // [ ]
350: // [ ]
351: for (int j = 0; j < firstAffectedOrdinate; j++) {
352: matrix.setElement(j, j, 1);
353: }
354: // Set central part: [ 1 0 0 0 0 0 ]
355: // [ 0 1 0 0 0 0 ]
356: // [ 0 0 ? ? ? 0 ]
357: // [ 0 0 ? ? ? 0 ]
358: // [ ]
359: subMatrix.copySubMatrix(0, 0, numRow, numCol,
360: firstAffectedOrdinate, firstAffectedOrdinate, matrix);
361:
362: // Set LR part to 1: [ 1 0 0 0 0 0 ]
363: // [ 0 1 0 0 0 0 ]
364: // [ 0 0 ? ? ? 0 ]
365: // [ 0 0 ? ? ? 0 ]
366: // [ 0 0 0 0 0 1 ]
367: final int offset = numCol - numRow;
368: final int numRowOut = numRow + nSkipped;
369: for (int j = numRowOut - numTrailingOrdinates; j < numRowOut; j++) {
370: matrix.setElement(j, j + offset, 1);
371: }
372: if (affine != 0) {
373: // Copy the translation terms in the last column.
374: subMatrix.copySubMatrix(0, numCol, numRow, affine,
375: firstAffectedOrdinate, numCol + nSkipped, matrix);
376: // Copy the last row as a safety, but it should contains only 0.
377: subMatrix.copySubMatrix(numRow, 0, affine, numCol, numRow
378: + nSkipped, firstAffectedOrdinate, matrix);
379: // Copy the lower right corner, which should contains only 1.
380: subMatrix.copySubMatrix(numRow, numCol, affine, affine,
381: numRow + nSkipped, numCol + nSkipped, matrix);
382: }
383: return matrix;
384: }
385:
386: /**
387: * Creates the inverse transform of this object.
388: */
389: public MathTransform inverse()
390: throws NoninvertibleTransformException {
391: // No need to synchronize. Not a big deal if two objects are created.
392: if (inverse == null) {
393: inverse = new PassThroughTransform(firstAffectedOrdinate,
394: subTransform.inverse(), numTrailingOrdinates);
395: inverse.inverse = this ;
396: }
397: return inverse;
398: }
399:
400: /**
401: * Returns a hash value for this transform.
402: * This value need not remain consistent between
403: * different implementations of the same class.
404: */
405: public int hashCode() {
406: int code = (int) serialVersionUID + firstAffectedOrdinate + 37
407: * numTrailingOrdinates;
408: if (subTransform != null) {
409: code ^= subTransform.hashCode();
410: }
411: return code;
412: }
413:
414: /**
415: * Compares the specified object with this math transform for equality.
416: */
417: public boolean equals(final Object object) {
418: if (object == this ) {
419: return true;
420: }
421: if (super .equals(object)) {
422: final PassThroughTransform that = (PassThroughTransform) object;
423: return this .firstAffectedOrdinate == that.firstAffectedOrdinate
424: && this .numTrailingOrdinates == that.numTrailingOrdinates
425: && Utilities.equals(this .subTransform,
426: that.subTransform);
427: }
428: return false;
429: }
430:
431: /**
432: * Format the inner part of a
433: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
434: * Known Text</cite> (WKT)</A> element.
435: *
436: * @param formatter The formatter to use.
437: * @return The WKT element name.
438: *
439: * @todo The {@link #numTrailingOrdinates} parameter is not part of OpenGIS specification.
440: * We should returns a more complex WKT when {@code numTrailingOrdinates != 0},
441: * using an affine transform to change the coordinates order.
442: */
443: protected String formatWKT(final Formatter formatter) {
444: formatter.append(firstAffectedOrdinate);
445: if (numTrailingOrdinates != 0) {
446: formatter.append(numTrailingOrdinates);
447: }
448: formatter.append(subTransform);
449: return "PASSTHROUGH_MT";
450: }
451: }
|