001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
006: * (C) 2002, 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.builder;
019:
020: // J2SE dependencies
021: import java.awt.Point;
022: import java.awt.geom.AffineTransform;
023: import java.awt.geom.NoninvertibleTransformException;
024: import java.awt.geom.Point2D;
025: import java.io.IOException;
026: import java.io.ObjectInputStream;
027: import java.io.Serializable;
028: import java.util.Arrays;
029:
030: // OpenGIS dependencies
031: import org.opengis.parameter.ParameterValue;
032: import org.opengis.parameter.ParameterValueGroup;
033: import org.opengis.parameter.ParameterDescriptor;
034: import org.opengis.parameter.ParameterDescriptorGroup;
035: import org.opengis.parameter.ParameterNotFoundException;
036: import org.opengis.referencing.FactoryException;
037: import org.opengis.referencing.operation.MathTransform;
038: import org.opengis.referencing.operation.MathTransform2D;
039: import org.opengis.referencing.operation.Matrix;
040: import org.opengis.referencing.operation.Transformation;
041: import org.opengis.referencing.operation.TransformException;
042:
043: // Geotools dependencies
044: import org.geotools.metadata.iso.citation.Citations;
045: import org.geotools.parameter.Parameter;
046: import org.geotools.parameter.ParameterGroup;
047: import org.geotools.referencing.NamedIdentifier;
048: import org.geotools.referencing.operation.MathTransformProvider;
049: import org.geotools.referencing.operation.matrix.Matrix2;
050: import org.geotools.referencing.operation.transform.AbstractMathTransform;
051: import org.geotools.util.logging.Logging;
052: import org.geotools.resources.Utilities;
053: import org.geotools.resources.i18n.Errors;
054: import org.geotools.resources.i18n.ErrorKeys;
055:
056: /**
057: * Transform a set of coordinate points using a grid of localization.
058: * Input coordinates are index in this two-dimensional array.
059: * Those input coordinates (or index) should be in the range
060: *
061: * <code>x</sub>input</sub> = [0..width-1]</code> and
062: * <code>y</sub>input</sub> = [0..height-1]</code> inclusive,
063: *
064: * where {@code width} and {@code height} are the number of columns and
065: * rows in the grid of localization. Output coordinates are the values stored in
066: * the grid of localization at the specified index. If input coordinates (index)
067: * are non-integer values, then output coordinates are interpolated using a bilinear
068: * interpolation. If input coordinates are outside the grid range, then output
069: * coordinates are extrapolated.
070: *
071: * @since 2.0
072: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/builder/LocalizationGridTransform2D.java $
073: * @version $Id: LocalizationGridTransform2D.java 29101 2008-02-06 12:11:19Z desruisseaux $
074: * @author Remi Eve
075: * @author Martin Desruisseaux
076: *
077: * @todo This class should extends {@link WarpTransform2D} and constructs a
078: * {@link javax.media.jai.WarpGrid} the first time the {@link WarpTransform2D#getWarp()}
079: * method is invoked (GEOT-522).
080: */
081: final class LocalizationGridTransform2D extends AbstractMathTransform
082: implements MathTransform2D, Serializable {
083: /**
084: * Serial number for interoperability with different versions.
085: */
086: private static final long serialVersionUID = 1067560328828441295L;
087:
088: /**
089: * Maximal number of iterations to try before to fail
090: * during an inverse transformation.
091: */
092: private static final int MAX_ITER = 40;
093:
094: /**
095: * Set to {@code true} for a conservative (and maybe slower) algorithm
096: * in {@link #inverseTransform}.
097: */
098: private static final boolean CONSERVATIVE = true;
099:
100: /**
101: * Set to {@code true} for forcing {@link #inverseTransform} to returns
102: * a value instead of throwing an exception if the transform do not converge.
103: * This is a temporary flag until we find why the inverse transform fails to
104: * converge in some case.
105: */
106: private static final boolean MASK_NON_CONVERGENCE;
107: static {
108: String property;
109: try {
110: property = System.getProperty(
111: "org.geotools.referencing.forceConvergence",
112: "false");
113: } catch (SecurityException exception) {
114: // We are running in an applet.
115: property = "false";
116: }
117: MASK_NON_CONVERGENCE = property.equalsIgnoreCase("true");
118: }
119:
120: /**
121: * <var>x</var> (usually longitude) offset relative to an entry.
122: * Points are stored in {@link #grid} as {@code (x,y)} pairs.
123: */
124: static final int X_OFFSET = 0;
125:
126: /**
127: * <var>y</var> (usually latitude) offset relative to an entry.
128: * Points are stored in {@link #grid} as {@code (x,y)} pairs.
129: */
130: static final int Y_OFFSET = 1;
131:
132: /**
133: * Length of an entry in the {@link #grid} array. This lenght
134: * is equals to the dimension of output coordinate points.
135: */
136: static final int CP_LENGTH = 2;
137:
138: /**
139: * Number of grid's columns.
140: */
141: private final int width;
142:
143: /**
144: * Number of grid's rows.
145: */
146: private final int height;
147:
148: /**
149: * Grid of coordinate points.
150: * Points are stored as {@code (x,y)} pairs.
151: */
152: private final double[] grid;
153:
154: /**
155: * A global affine transform for the whole grid.
156: */
157: private final AffineTransform global;
158:
159: /**
160: * The inverse math transform. Will be constructed only when first requested.
161: */
162: private transient MathTransform inverse;
163:
164: /**
165: * Constructs a localization grid using the specified data.
166: *
167: * @param width Number of grid's columns.
168: * @param height Number of grid's rows.
169: * @param grid The localization grid as an array of {@code (x,y)} coordinates.
170: * This array is not cloned; this is the caller's responsability to ensure
171: * that it will not be modified as long as this transformation is strongly
172: * reachable.
173: * @param global A global affine transform for the whole grid.
174: */
175: protected LocalizationGridTransform2D(final int width,
176: final int height, final double[] grid,
177: final AffineTransform global) {
178: this .width = width;
179: this .height = height;
180: this .grid = grid;
181: this .global = global;
182: }
183:
184: /**
185: * Returns the parameter descriptors for this math transform.
186: */
187: public ParameterDescriptorGroup getParameterDescriptors() {
188: return Provider.PARAMETERS;
189: }
190:
191: /**
192: * Returns the dimension of input points.
193: */
194: public int getSourceDimensions() {
195: return 2;
196: }
197:
198: /**
199: * Returns the dimension of output points.
200: */
201: public int getTargetDimensions() {
202: return 2;
203: }
204:
205: /**
206: * Tests if this transform is the identity transform.
207: */
208: public boolean isIdentity() {
209: return false;
210: }
211:
212: /**
213: * Gets the derivative of this transform at a point.
214: */
215: public Matrix derivative(final Point2D point) {
216: final AffineTransform tr = new AffineTransform();
217: getAffineTransform(point.getX(), point.getY(), tr);
218: return new Matrix2(tr.getScaleX(), tr.getShearX(), tr
219: .getShearY(), tr.getScaleY());
220: }
221:
222: /**
223: * Transforme des coordonnées sources (généralement des index de pixels) en coordonnées
224: * destinations (généralement des degrés de longitude et latitude). Les transformations
225: * feront intervenir des interpolations linéaires si les coordonnées sources ne sont pas
226: * entières.
227: *
228: * @param srcPts Points d'entrée.
229: * @param srcOff Index du premier point d'entrée à transformer.
230: * @param dstPts Points de sortie.
231: * @param dstOff Index du premier point de sortie.
232: * @param numPts Nombre de points à transformer.
233: */
234: public void transform(final float[] srcPts, int srcOff,
235: final float[] dstPts, int dstOff, int numPts) {
236: transform(srcPts, null, srcOff, dstPts, null, dstOff, numPts);
237: }
238:
239: /**
240: * Transforme des coordonnées sources (généralement des index de pixels) en coordonnées
241: * destinations (généralement des degrés de longitude et latitude). Les transformations
242: * feront intervenir des interpolations linéaires si les coordonnées sources ne sont pas
243: * entières.
244: *
245: * @param srcPts Points d'entrée.
246: * @param srcOff Index du premier point d'entrée à transformer.
247: * @param dstPts Points de sortie.
248: * @param dstOff Index du premier point de sortie.
249: * @param numPts Nombre de points à transformer.
250: */
251: public void transform(final double[] srcPts, int srcOff,
252: final double[] dstPts, int dstOff, int numPts) {
253: transform(null, srcPts, srcOff, null, dstPts, dstOff, numPts);
254: }
255:
256: /**
257: * Implementation of direct transformation.
258: */
259: private void transform(final float[] srcPts1,
260: final double[] srcPts2, int srcOff, final float[] dstPts1,
261: final double[] dstPts2, int dstOff, int numPts) {
262: final int minCol = 0;
263: final int minRow = 0;
264: final int maxCol = width - 2;
265: final int maxRow = height - 2;
266: int postIncrement = 0;
267: if (srcOff < dstOff) {
268: if ((srcPts2 != null) ? srcPts2 == dstPts2
269: : srcPts1 == dstPts1) {
270: srcOff += (numPts - 1) * 2;
271: dstOff += (numPts - 1) * 2;
272: postIncrement = -4;
273: }
274: }
275: while (--numPts >= 0) {
276: final double xi, yi;
277: if (srcPts2 != null) {
278: xi = srcPts2[srcOff++];
279: yi = srcPts2[srcOff++];
280: } else {
281: xi = srcPts1[srcOff++];
282: yi = srcPts1[srcOff++];
283: }
284: final int col = Math
285: .max(Math.min((int) xi, maxCol), minCol);
286: final int row = Math
287: .max(Math.min((int) yi, maxRow), minRow);
288: final int offset00 = (col + row * width) * CP_LENGTH;
289: final int offset01 = offset00 + CP_LENGTH * width; // Une ligne plus bas
290: final int offset10 = offset00 + CP_LENGTH; // Une colonne à droite
291: final int offset11 = offset01 + CP_LENGTH; // Une colonne à droite, une ligne plus bas
292: /*
293: * Interpole les coordonnées de destination [00]--.(x0,y0)----[10]
294: * sur la ligne courante (x0,y0) ainsi que | |
295: * sur la ligne suivante (x1,y1). Exemple | .(x,y) |
296: * ci-contre: les coordonnées sources sont | |
297: * entre crochets, et les coordonnées de la [01]--.(x1,y1)----[11]
298: * sortie (à calculer) sont entre parenthèses.
299: */
300: final double x0 = linearInterpolation(col + 0,
301: grid[offset00 + X_OFFSET], col + 1, grid[offset10
302: + X_OFFSET], xi);
303: final double y0 = linearInterpolation(col + 0,
304: grid[offset00 + Y_OFFSET], col + 1, grid[offset10
305: + Y_OFFSET], xi);
306: final double x1 = linearInterpolation(col + 0,
307: grid[offset01 + X_OFFSET], col + 1, grid[offset11
308: + X_OFFSET], xi);
309: final double y1 = linearInterpolation(col + 0,
310: grid[offset01 + Y_OFFSET], col + 1, grid[offset11
311: + Y_OFFSET], xi);
312: /*
313: * Interpole maintenant les coordonnées (x,y) entre les deux lignes.
314: */
315: final double xf = linearInterpolation(row, x0, row + 1, x1,
316: yi);
317: final double yf = linearInterpolation(row, y0, row + 1, y1,
318: yi);
319: if (dstPts2 != null) {
320: dstPts2[dstOff++] = xf;
321: dstPts2[dstOff++] = yf;
322: } else {
323: dstPts1[dstOff++] = (float) xf;
324: dstPts1[dstOff++] = (float) yf;
325: }
326: srcOff += postIncrement;
327: dstOff += postIncrement;
328: if (false) {
329: final java.io.PrintStream out = System.out;
330: out.print("TD ==> xi : ");
331: out.print(xi);
332: out.print(" / yi : ");
333: out.print(yi);
334: out.print(" ---> xo : ");
335: out.print(xf);
336: out.print(" / yo : ");
337: out.println(yf);
338: }
339: }
340: }
341:
342: /**
343: * Interpole/extrapole entre deux points.
344: *
345: * @param x1 Coordonnee <var>x</var> du premier point.
346: * @param y1 Coordonnee <var>y</var> du premier point.
347: * @param x2 Coordonnee <var>x</var> du second point.
348: * @param y2 Coordonnee <var>y</var> du second point.
349: * @param x Position <var>x</var> à laquelle calculer la valeur de <var>y</var>.
350: * @return La valeur <var>y</var> interpolée entre les deux points.
351: */
352: private static double linearInterpolation(final double x1,
353: final double y1, final double x2, final double y2,
354: final double x) {
355: return y1 + (y2 - y1) / (x2 - x1) * (x - x1);
356: }
357:
358: /**
359: * Retourne une approximation de la transformation affine à la position indiquée.
360: *
361: * @param col Coordonnee <var>x</var> du point.
362: * @param row Coordonnee <var>y</var> du point.
363: * @param dest Matrice dans laquelle écrire la transformation affine.
364: */
365: private void getAffineTransform(double x, double y,
366: final AffineTransform dest) {
367: int col = (int) x;
368: int row = (int) y;
369: if (col > width - 2)
370: col = width - 2;
371: if (row > height - 2)
372: row = height - 2;
373: if (col < 0)
374: col = 0;
375: if (row < 0)
376: row = 0;
377: final int sgnCol;
378: final int sgnRow;
379: if (x - col > 0.5) {
380: sgnCol = -1;
381: col++;
382: } else
383: sgnCol = +1;
384: if (y - row > 0.5) {
385: sgnRow = -1;
386: row++;
387: } else
388: sgnRow = +1;
389: /*
390: * Le calcul de la transformation affine comprend 6 P00------P10
391: * inconnues. Sa solution recquiert donc 6 équations. | |
392: * Nous les obtenons en utilisant 3 points, chaque | |
393: * points ayant 2 coordonnées. Voir exemple ci-contre: P01----(ignoré)
394: */
395: final int offset00 = (col + row * width) * CP_LENGTH;
396: final int offset01 = offset00 + sgnRow * CP_LENGTH * width;
397: final int offset10 = offset00 + sgnCol * CP_LENGTH;
398: x = grid[offset00 + X_OFFSET];
399: y = grid[offset00 + Y_OFFSET];
400: final double dxCol = (grid[offset10 + X_OFFSET] - x) * sgnCol;
401: final double dyCol = (grid[offset10 + Y_OFFSET] - y) * sgnCol;
402: final double dxRow = (grid[offset01 + X_OFFSET] - x) * sgnRow;
403: final double dyRow = (grid[offset01 + Y_OFFSET] - y) * sgnRow;
404: dest.setTransform(dxCol, dyCol, dxRow, dyRow, x - dxCol * col
405: - dxRow * row, y - dyCol * col - dyRow * row);
406: /*
407: * Si l'on transforme les 3 points qui ont servit à déterminer la transformation
408: * affine, on devrait obtenir un résultat identique (aux erreurs d'arrondissement
409: * près) peu importe que l'on utilise la transformation affine ou la grille de
410: * localisation.
411: */
412: assert distance(new Point(col, row), dest) < 1E-5;
413: assert distance(new Point(col + sgnCol, row), dest) < 1E-5;
414: assert distance(new Point(col, row + sgnRow), dest) < 1E-5;
415: }
416:
417: /**
418: * Transform a point using the localization grid, transform it back using the inverse
419: * of the specified affine transform, and returns the distance between the source and
420: * the resulting point. This is used for assertions only.
421: *
422: * @param index The source point to test.
423: * @param tr The affine transform to test.
424: * @return The distance in grid coordinate. Should be close to 0.
425: */
426: private double distance(final Point2D index,
427: final AffineTransform tr) {
428: try {
429: Point2D geoCoord = transform(index, null);
430: geoCoord = tr.inverseTransform(geoCoord, geoCoord);
431: return geoCoord.distance(index);
432: } catch (TransformException exception) {
433: // Should not happen
434: throw new AssertionError(exception);
435: } catch (NoninvertibleTransformException exception) {
436: // Not impossible. What should we do? Open question...
437: throw new AssertionError(exception);
438: }
439: }
440:
441: /**
442: * Apply the inverse transform to a set of points. More specifically, this method transform
443: * "real world" coordinates to grid coordinates. This method use an iterative algorithm for
444: * that purpose. A {@link TransformException} is thrown in the computation do not converge.
445: * The algorithm applied by this method and its callers is:
446: *
447: * <ul>
448: * <li>Transform the first point using a "global" affine transform (i.e. the affine
449: * transformed computed using the "least squares" method in LocalizationGrid).
450: * Other points will be transformed using the last successful affine transform,
451: * since we assume that the points to transform are close to each other.</li>
452: *
453: * <li>Next, compute a local affine transform and use if for transforming the point
454: * again. Recompute again the local affine transform and continue until the cell
455: * (x0,y0) doesn't change.</li>
456: * </ul>
457: *
458: * @param source The "real world" coordinate to transform.
459: * @param target A pre-allocated destination point. <strong>This point
460: * can't be the same than {@code source}!<strong>
461: * @param tr In input, the affine transform to use for the first step.
462: * In output, the last affine transform used for the transformation.
463: *
464: */
465: final void inverseTransform(final Point2D source,
466: final Point2D.Double target, final AffineTransform tr)
467: throws TransformException {
468: if (CONSERVATIVE) {
469: // In an optimal approach, we should reuse the same affine transform than the one used
470: // in the last transformation, since it is likely to converge faster for a point close
471: // to the previous one. However, it may lead to strange and hard to predict
472: // discontinuity in transformations.
473: tr.setTransform(global);
474: }
475: try {
476: tr.inverseTransform(source, target);
477: int previousX = (int) target.x;
478: int previousY = (int) target.y;
479: for (int iter = 0; iter < MAX_ITER; iter++) {
480: getAffineTransform(target.x, target.y, tr);
481: tr.inverseTransform(source, target);
482: final int ix = (int) target.x;
483: final int iy = (int) target.y;
484: if (previousX == ix && previousY == iy) {
485: // Computation converged.
486: if (target.x >= 0 && target.x < width
487: && target.y >= 0 && target.y < height) {
488: // Point is inside the grid. Check the precision.
489: assert transform(target, null).distanceSq(
490: source) < 1E-3 : target;
491: } else {
492: // Point is outside the grid. Use the global transform for uniformity.
493: inverseTransform(source, target);
494: }
495: return;
496: }
497: previousX = ix;
498: previousY = iy;
499: }
500: /*
501: * No convergence found in the "ordinary" loop. The following code checks if
502: * we are stuck in a never-ending loop. If yes, then it will try to minimize
503: * the following function:
504: *
505: * {@code transform(target).distance(source)}.
506: */
507: final int x0 = previousX;
508: final int y0 = previousY;
509: global.inverseTransform(source, target);
510: double x, y;
511: double bestX = x = target.x;
512: double bestY = y = target.y;
513: double minSq = Double.POSITIVE_INFINITY;
514: for (int iter = 1 - MAX_ITER; iter < MAX_ITER; iter++) {
515: previousX = (int) x;
516: previousY = (int) y;
517: getAffineTransform(x, y, tr);
518: tr.inverseTransform(source, target);
519: x = target.x;
520: y = target.y;
521: final int ix = (int) x;
522: final int iy = (int) y;
523: if (previousX == ix && previousY == iy) {
524: // Computation converged.
525: assert iter >= 0;
526: if (x >= 0 && x < width && y >= 0 && y < height) {
527: // Point is inside the grid. Check the precision.
528: assert transform(target, null).distanceSq(
529: source) < 1E-3 : target;
530: } else {
531: // Point is outside the grid. Use the global transform for uniformity.
532: inverseTransform(source, target);
533: }
534: return;
535: }
536: if (iter == 0) {
537: assert x0 == ix && y0 == iy;
538: } else if (x0 == ix && y0 == iy) {
539: // Loop detected.
540: if (bestX >= 0 && bestX < width && bestY >= 0
541: && bestY < height) {
542: target.x = bestX;
543: target.y = bestY;
544: } else {
545: inverseTransform(source, target);
546: }
547: return;
548: }
549: transform(target, target);
550: final double distanceSq = target.distanceSq(source);
551: if (distanceSq < minSq) {
552: minSq = distanceSq;
553: bestX = x;
554: bestY = y;
555: }
556: }
557: /*
558: * The transformation didn't converge, and no loop has been found.
559: * If the following block is enabled (true), then the best point
560: * will be returned. It may not be the best approach since we don't
561: * know if this point is valid. Otherwise, an exception is thrown.
562: */
563: if (MASK_NON_CONVERGENCE) {
564: Logging.getLogger("org.geotools.gc").fine(
565: "No convergence");
566: if (bestX >= 0 && bestX < width && bestY >= 0
567: && bestY < height) {
568: target.x = bestX;
569: target.y = bestY;
570: } else {
571: inverseTransform(source, target);
572: }
573: return;
574: }
575: } catch (NoninvertibleTransformException exception) {
576: final TransformException e;
577: e = new TransformException(Errors
578: .format(ErrorKeys.NONINVERTIBLE_TRANSFORM));
579: e.initCause(exception);
580: throw e;
581: }
582: throw new TransformException(Errors
583: .format(ErrorKeys.NO_CONVERGENCE));
584: }
585:
586: /**
587: * Inverse transforms a point using the {@link #global} affine transform, and
588: * make sure that the result point is outside the grid. This method is used
589: * for the transformation of a point which shouldn't be found in the grid.
590: *
591: * @param source The source coordinate point.
592: * @param target The target coordinate point (should not be {@code null}).
593: * @throws NoninvertibleTransformException if the transform is non-invertible.
594: *
595: * @todo Current implementation project an inside point on the nearest border.
596: * Could we do something better?
597: */
598: private void inverseTransform(final Point2D source,
599: final Point2D.Double target)
600: throws NoninvertibleTransformException {
601: if (global.inverseTransform(source, target) != target) {
602: throw new AssertionError(); // Should not happen.
603: }
604: double x = target.x;
605: double y = target.y;
606: if (x >= 0 && x < width && y >= 0 && y < height) {
607: // Project on the nearest border. TODO: Could we do something better here?
608: x -= 0.5 * width;
609: y -= 0.5 * height;
610: if (Math.abs(x) < Math.abs(y)) {
611: target.x = x > 0 ? width : -1;
612: } else {
613: target.y = y > 0 ? height : -1;
614: }
615: }
616: }
617:
618: /**
619: * Returns the inverse transform.
620: */
621: public MathTransform inverse() {
622: if (inverse == null) {
623: inverse = new Inverse();
624: }
625: return inverse;
626: }
627:
628: /**
629: * The inverse transform. This inner class is the inverse of the enclosing math transform.
630: *
631: * @version $Id: LocalizationGridTransform2D.java 29101 2008-02-06 12:11:19Z desruisseaux $
632: * @author Martin Desruisseaux
633: */
634: private final class Inverse extends AbstractMathTransform.Inverse
635: implements MathTransform2D, Serializable {
636: /**
637: * Serial number for interoperability with different versions.
638: */
639: private static final long serialVersionUID = 4876426825123740986L;
640:
641: /**
642: * Default constructor.
643: */
644: public Inverse() {
645: LocalizationGridTransform2D.this .super ();
646: }
647:
648: /**
649: * Transform a "real world" coordinate into a grid coordinate.
650: */
651: public Point2D transform(final Point2D ptSrc,
652: final Point2D ptDst) throws TransformException {
653: final AffineTransform tr = new AffineTransform(global);
654: if (ptDst == null) {
655: final Point2D.Double target = new Point2D.Double();
656: inverseTransform(ptSrc, target, tr);
657: return target;
658: }
659: if (ptDst != ptSrc && (ptDst instanceof Point2D.Double)) {
660: inverseTransform(ptSrc, (Point2D.Double) ptDst, tr);
661: return ptDst;
662: }
663: final Point2D.Double target = new Point2D.Double();
664: inverseTransform(ptSrc, target, tr);
665: ptDst.setLocation(target);
666: return ptDst;
667: }
668:
669: /**
670: * Apply the inverse transform to a set of points. More specifically, this method transform
671: * "real world" coordinates to grid coordinates. This method use an iterative algorithm for
672: * that purpose. A {@link TransformException} is thrown in the computation do not converge.
673: *
674: * @param srcPts the array containing the source point coordinates.
675: * @param srcOff the offset to the first point to be transformed in the source array.
676: * @param dstPts the array into which the transformed point coordinates are returned.
677: * May be the same than {@code srcPts}.
678: * @param dstOff the offset to the location of the first transformed
679: * point that is stored in the destination array.
680: * @param numPts the number of point objects to be transformed.
681: * @throws TransformException if a point can't be transformed.
682: */
683: public void transform(final float[] srcPts, int srcOff,
684: final float[] dstPts, int dstOff, int numPts)
685: throws TransformException {
686: int postIncrement = 0;
687: if (srcPts == dstPts && srcOff < dstOff) {
688: srcOff += (numPts - 1) * 2;
689: dstOff += (numPts - 1) * 2;
690: postIncrement = -4;
691: }
692: final Point2D.Double source = new Point2D.Double();
693: final Point2D.Double target = new Point2D.Double();
694: final AffineTransform tr = new AffineTransform(global);
695: while (--numPts >= 0) {
696: source.x = srcPts[srcOff++];
697: source.y = srcPts[srcOff++];
698: inverseTransform(source, target, tr);
699: dstPts[dstOff++] = (float) target.x;
700: dstPts[dstOff++] = (float) target.y;
701: srcOff += postIncrement;
702: dstOff += postIncrement;
703: }
704: }
705:
706: /**
707: * Apply the inverse transform to a set of points. More specifically, this method transform
708: * "real world" coordinates to grid coordinates. This method use an iterative algorithm for
709: * that purpose. A {@link TransformException} is thrown in the computation do not converge.
710: *
711: * @param srcPts the array containing the source point coordinates.
712: * @param srcOff the offset to the first point to be transformed in the source array.
713: * @param dstPts the array into which the transformed point coordinates are returned.
714: * May be the same than {@code srcPts}.
715: * @param dstOff the offset to the location of the first transformed
716: * point that is stored in the destination array.
717: * @param numPts the number of point objects to be transformed.
718: * @throws TransformException if a point can't be transformed.
719: */
720: public void transform(final double[] srcPts, int srcOff,
721: final double[] dstPts, int dstOff, int numPts)
722: throws TransformException {
723: int postIncrement = 0;
724: if (srcPts == dstPts && srcOff < dstOff) {
725: srcOff += (numPts - 1) * 2;
726: dstOff += (numPts - 1) * 2;
727: postIncrement = -4;
728: }
729: final Point2D.Double source = new Point2D.Double();
730: final Point2D.Double target = new Point2D.Double();
731: final AffineTransform tr = new AffineTransform(global);
732: while (--numPts >= 0) {
733: source.x = srcPts[srcOff++];
734: source.y = srcPts[srcOff++];
735: inverseTransform(source, target, tr);
736: dstPts[dstOff++] = target.x;
737: dstPts[dstOff++] = target.y;
738: srcOff += postIncrement;
739: dstOff += postIncrement;
740: }
741: }
742:
743: /**
744: * Restore reference to this object after deserialization.
745: */
746: private void readObject(ObjectInputStream in)
747: throws IOException, ClassNotFoundException {
748: in.defaultReadObject();
749: LocalizationGridTransform2D.this .inverse = this ;
750: }
751: }
752:
753: /**
754: * Returns a hash value for this transform.
755: */
756: public int hashCode() {
757: return (int) serialVersionUID ^ super .hashCode()
758: ^ global.hashCode();
759: }
760:
761: /**
762: * Compare this transform with the specified object for equality.
763: */
764: public boolean equals(final Object object) {
765: if (super .equals(object)) {
766: final LocalizationGridTransform2D that = (LocalizationGridTransform2D) object;
767: return this .width == that.width
768: && this .height == that.height
769: && Utilities.equals(this .global, that.global)
770: && Arrays.equals(this .grid, that.grid);
771: }
772: return false;
773: }
774:
775: /**
776: * The provider for the {@link LocalizationGridTransform2D}.
777: *
778: * @version $Id: LocalizationGridTransform2D.java 29101 2008-02-06 12:11:19Z desruisseaux $
779: * @author Martin Desruisseaux
780: *
781: * @todo Not yet fully implemented. Once it is implemented, we need to add a
782: * getParameterValues() method in LocalizationGridTransform2D.
783: */
784: private static class Provider extends MathTransformProvider {
785: /** Serial number for interoperability with different versions. */
786: private static final long serialVersionUID = -8263439392080019340L;
787:
788: /**
789: * The parameters group.
790: */
791: static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
792: new NamedIdentifier[] { new NamedIdentifier(
793: Citations.GEOTOOLS, "WarpPolynomial") },
794: new ParameterDescriptor[] {});
795:
796: /**
797: * Create a provider for warp transforms.
798: */
799: public Provider() {
800: super (2, 2, PARAMETERS);
801: }
802:
803: /**
804: * Returns the operation type.
805: */
806: public Class getOperationType() {
807: return Transformation.class;
808: }
809:
810: /**
811: * Creates a warp transform from the specified group of parameter values.
812: *
813: * @param values The group of parameter values.
814: * @return The created math transform.
815: * @throws ParameterNotFoundException if a required parameter was not found.
816: */
817: protected MathTransform createMathTransform(
818: final ParameterValueGroup values)
819: throws ParameterNotFoundException, FactoryException {
820: throw new FactoryException("Not yet implemented");
821: }
822: }
823: }
|