001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2005-2006, Geotools Project Managment Committee (PMC)
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.referencing.operation.transform;
018:
019: // J2SE dependencies and extensions
020: import java.awt.geom.Point2D;
021: import java.awt.geom.Rectangle2D;
022: import java.io.Serializable;
023: import javax.units.Unit;
024:
025: // JAI dependencies
026: import javax.media.jai.Warp;
027: import javax.media.jai.WarpAffine;
028: import javax.media.jai.WarpQuadratic;
029: import javax.media.jai.WarpCubic;
030: import javax.media.jai.WarpPolynomial;
031: import javax.media.jai.WarpGeneralPolynomial;
032:
033: // OpenGIS dependencies
034: import org.opengis.util.InternationalString;
035: import org.opengis.parameter.ParameterValue;
036: import org.opengis.parameter.ParameterValueGroup;
037: import org.opengis.parameter.ParameterDescriptor;
038: import org.opengis.parameter.ParameterDescriptorGroup;
039: import org.opengis.parameter.ParameterNotFoundException;
040: import org.opengis.referencing.operation.MathTransform;
041: import org.opengis.referencing.operation.MathTransform2D;
042: import org.opengis.referencing.operation.NoninvertibleTransformException;
043: import org.opengis.referencing.operation.Transformation;
044:
045: // Geotools dependencies
046: import org.geotools.resources.Utilities;
047: import org.geotools.referencing.NamedIdentifier;
048: import org.geotools.referencing.operation.MathTransformProvider;
049: import org.geotools.metadata.iso.citation.Citations;
050: import org.geotools.parameter.DefaultParameterDescriptor;
051: import org.geotools.parameter.ParameterGroup;
052: import org.geotools.parameter.Parameter;
053: import org.geotools.resources.XArray;
054: import org.geotools.resources.i18n.Vocabulary;
055: import org.geotools.resources.i18n.VocabularyKeys;
056:
057: /**
058: * Wraps an arbitrary {@link Warp} object as a {@linkplain MathTransform2D two-dimensional transform}.
059: * Calls to {@linkplain #transform(float[],int,float[],int,int) transform} methods are forwarded to
060: * the {@link Warp#warpPoint(int,int,float[]) warpPoint} method, or something equivalent. This
061: * implies that source coordinates may be rounded to nearest integers before the transformation
062: * is applied.
063: * <p>
064: * This transform is typically used with {@linkplain org.geotools.coverage.processing.operation.Resample
065: * grid coverage "Resample" operation} for reprojecting an image. Source and destination coordinates
066: * are usually pixel coordinates in source and target image, which is why this transform may use
067: * integer arithmetic.
068: * <p>
069: * This math transform can be created alone (by invoking its public constructors directly), or it
070: * can be created by a factory like {@link LocalizationGrid}.
071: * <p>
072: * For more information on image warp, see
073: * <A HREF="http://java.sun.com/products/java-media/jai/forDevelopers/jai1_0_1guide-unc/Geom-image-manip.doc.html">Geometric
074: * Image Manipulation</A> in the <cite>Programming in Java Advanced Imaging</cite> guide.
075: *
076: * @since 2.1
077: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/WarpTransform2D.java $
078: * @version $Id: WarpTransform2D.java 29101 2008-02-06 12:11:19Z desruisseaux $
079: * @author Martin Desruisseaux
080: * @author Alessio Fabiani
081: *
082: * @see LocalizationGrid#getPolynomialTransform(int)
083: * @see Warp
084: * @see javax.media.jai.WarpOpImage
085: * @see javax.media.jai.operator.WarpDescriptor
086: */
087: public class WarpTransform2D extends AbstractMathTransform implements
088: MathTransform2D, Serializable {
089: /**
090: * Serial number for interoperability with different versions.
091: */
092: private static final long serialVersionUID = -7949539694656719923L;
093:
094: /**
095: * The maximal polynomial degree allowed.
096: *
097: * @since 2.4
098: */
099: public static final int MAX_DEGREE = 7;
100:
101: /**
102: * The warp object. Transformations will be applied using the
103: * {@link Warp#warpPoint(int,int,float[]) warpPoint} method or something equivalent.
104: */
105: private final Warp warp;
106:
107: /**
108: * The inverse math transform.
109: */
110: private final WarpTransform2D inverse;
111:
112: /**
113: * Constructs a warp transform that approximatively maps the given source coordinates to the
114: * given destination coordinates. The transformation is performed using some polynomial warp
115: * with the degree supplied in argument. The number of points required for each degree of warp
116: * are as follows:
117: * <p>
118: * <table>
119: * <tr><th>Degree of Warp</th><th>Number of Points</th></tr>
120: * <tr><td>1</td><td>3</td></tr>
121: * <tr><td>2</td><td>6</td></tr>
122: * <tr><td>3</td><td>10</td></tr>
123: * <tr><td>4</td><td>15</td></tr>
124: * <tr><td>5</td><td>21</td></tr>
125: * <tr><td>6</td><td>28</td></tr>
126: * <tr><td>7</td><td>36</td></tr>
127: * </table>
128: *
129: * @param srcCoords Source coordinates.
130: * @param dstCoords Destination coordinates.
131: * @param degree The desired degree of the warp polynomials.
132: */
133: public WarpTransform2D(final Point2D[] srcCoords,
134: final Point2D[] dstCoords, final int degree) {
135: this (null, srcCoords, 0, null, dstCoords, 0, Math.min(
136: srcCoords.length, dstCoords.length), degree);
137: }
138:
139: /**
140: * Constructs a warp transform that approximatively maps the given source coordinates to the
141: * given destination coordinates. The transformation is performed using some polynomial warp
142: * with the degree supplied in argument.
143: *
144: * @param srcBounds Bounding box of source coordinates, or {@code null} if unknow.
145: * @param srcCoords Source coordinates.
146: * @param srcOffset The inital entry of {@code srcCoords} to be used.
147: * @param dstBounds Bounding box of destination coordinates, or {@code null} if unknow.
148: * @param dstCoords Destination coordinates.
149: * @param dstOffset The inital entry of {@code destCoords} to be used.
150: * @param numCoords The number of coordinates from {@code srcCoords} and {@code destCoords}
151: * to be used.
152: * @param degree The desired degree of the warp polynomials.
153: */
154: public WarpTransform2D(final Rectangle2D srcBounds,
155: final Point2D[] srcCoords, final int srcOffset,
156: final Rectangle2D dstBounds, final Point2D[] dstCoords,
157: final int dstOffset, final int numCoords, final int degree) {
158: this (srcBounds, toFloat(srcCoords, srcOffset, numCoords), 0,
159: dstBounds, toFloat(dstCoords, dstOffset, numCoords), 0,
160: numCoords, degree, false);
161: }
162:
163: /**
164: * Converts an array of points into an array of floats.
165: * This is used internally for the above constructor only.
166: */
167: private static float[] toFloat(final Point2D[] points, int offset,
168: final int numCoords) {
169: final float[] array = new float[numCoords * 2];
170: for (int i = 0; i < array.length;) {
171: final Point2D point = points[offset++];
172: array[i++] = (float) point.getX();
173: array[i++] = (float) point.getY();
174: }
175: return array;
176: }
177:
178: /**
179: * Constructs a warp transform that approximatively maps the given source coordinates to the
180: * given destination coordinates. The transformation is performed using some polynomial warp
181: * with the degree supplied in argument.
182: *
183: * @param srcBounds Bounding box of source coordinates, or {@code null} if unknow.
184: * @param srcCoords Source coordinates with <var>x</var> and <var>y</var> alternating.
185: * @param srcOffset The inital entry of {@code srcCoords} to be used.
186: * @param dstBounds Bounding box of destination coordinates, or {@code null} if unknow.
187: * @param dstCoords Destination coordinates with <var>x</var> and <var>y</var> alternating.
188: * @param dstOffset The inital entry of {@code destCoords} to be used.
189: * @param numCoords The number of coordinates from {@code srcCoords} and {@code destCoords}
190: * to be used.
191: * @param degree The desired degree of the warp polynomials.
192: */
193: public WarpTransform2D(final Rectangle2D srcBounds,
194: final float[] srcCoords, final int srcOffset,
195: final Rectangle2D dstBounds, final float[] dstCoords,
196: final int dstOffset, final int numCoords, final int degree) {
197: this (srcBounds, srcCoords, srcOffset, dstBounds, dstCoords,
198: dstOffset, numCoords, degree, true);
199: }
200:
201: /**
202: * Work around for a bug in WarpPolynomial.createWarp(...). This constructor should move in
203: * the one above when the {@code cloneCoords} argument will no longer be needed (after the
204: * JAI bug get fixed).
205: */
206: private WarpTransform2D(final Rectangle2D srcBounds,
207: float[] srcCoords, int srcOffset,
208: final Rectangle2D dstBounds, float[] dstCoords,
209: int dstOffset, final int numCoords, final int degree,
210: boolean cloneCoords) {
211: final float preScaleX, preScaleY, postScaleX, postScaleY;
212: if (srcBounds != null) {
213: preScaleX = (float) srcBounds.getWidth();
214: preScaleY = (float) srcBounds.getHeight();
215: } else {
216: preScaleX = getWidth(srcCoords, srcOffset, numCoords);
217: preScaleY = getWidth(srcCoords, srcOffset + 1, numCoords);
218: }
219: if (dstBounds != null) {
220: postScaleX = (float) dstBounds.getWidth();
221: postScaleY = (float) dstBounds.getHeight();
222: } else {
223: postScaleX = getWidth(dstCoords, dstOffset, numCoords);
224: postScaleY = getWidth(dstCoords, dstOffset + 1, numCoords);
225: }
226: /*
227: * Workaround for a bug in WarpPolynomial.create(...): the later scale coordinates
228: * according the scale values, but the 'preScale' and 'postScale' are interchanged.
229: * When JAI bug will be fixed, delete all the following block until the next comment.
230: */
231: final double scaleX = preScaleX / postScaleX;
232: final double scaleY = preScaleY / postScaleY;
233: if (scaleX != 1 || scaleY != 1) {
234: final int n = numCoords * 2;
235: if (cloneCoords) {
236: float[] o;
237: o = srcCoords;
238: srcCoords = new float[n];
239: System.arraycopy(o, srcOffset, srcCoords, 0, n);
240: srcOffset = 0;
241: o = dstCoords;
242: dstCoords = new float[n];
243: System.arraycopy(o, dstOffset, dstCoords, 0, n);
244: dstOffset = 0;
245: }
246: for (int i = 0; i < n;) {
247: srcCoords[srcOffset + i] /= scaleX;
248: dstCoords[dstOffset + i++] *= scaleX;
249: srcCoords[srcOffset + i] /= scaleY;
250: dstCoords[dstOffset + i++] *= scaleY;
251: }
252: }
253: /*
254: * Note: Warp semantic (transforms coordinates from destination to source) is the
255: * opposite of MathTransform semantic (transforms coordinates from source to
256: * destination). We have to interchange source and destination arrays for the
257: * direct transform.
258: */
259: warp = WarpPolynomial.createWarp(dstCoords, dstOffset,
260: srcCoords, srcOffset, numCoords, 1 / preScaleX,
261: 1 / preScaleY, postScaleX, postScaleY, degree);
262: inverse = new WarpTransform2D(WarpPolynomial.createWarp(
263: srcCoords, srcOffset, dstCoords, dstOffset, numCoords,
264: 1 / postScaleX, 1 / postScaleY, preScaleX, preScaleY,
265: degree), this );
266: }
267:
268: /**
269: * Returns the maximum minus the minimum ordinate int the specifie array.
270: * This is used internally for the above constructor only.
271: */
272: private static float getWidth(final float[] array, int offset,
273: int num) {
274: float min = Float.POSITIVE_INFINITY;
275: float max = Float.NEGATIVE_INFINITY;
276: while (--num >= 0) {
277: float value = array[offset];
278: if (value < min)
279: min = value;
280: if (value > max)
281: max = value;
282: offset += 2;
283: }
284: return max - min;
285: }
286:
287: /**
288: * Constructs a transform using the specified warp object. Transformations will be applied
289: * using the {@link Warp#warpPoint(int,int,float[]) warpPoint} method or something equivalent.
290: *
291: * @param warp The image warp to wrap into a math transform.
292: * @param inverse An image warp to uses for the {@linkplain #inverse inverse transform},
293: * or {@code null} in none.
294: */
295: protected WarpTransform2D(final Warp warp, final Warp inverse) {
296: ensureNonNull("warp", warp);
297: this .warp = warp;
298: this .inverse = (inverse != null) ? new WarpTransform2D(inverse,
299: this ) : null;
300: }
301:
302: /**
303: * Constructs a transform using the specified warp object. This private constructor is used
304: * for the construction of {@link #inverse} transform only.
305: */
306: private WarpTransform2D(final Warp warp,
307: final WarpTransform2D inverse) {
308: this .warp = warp;
309: this .inverse = inverse;
310: }
311:
312: /**
313: * Returns a transform using the specified warp object. Transformations will be applied
314: * using the {@link Warp#warpPoint(int,int,float[]) warpPoint} method or something equivalent.
315: *
316: * @param warp The image warp to wrap into a math transform.
317: */
318: public static MathTransform2D create(final Warp warp) {
319: if (warp instanceof WarpAdapter) {
320: return ((WarpAdapter) warp).getTransform();
321: }
322: return new WarpTransform2D(warp, (Warp) null);
323: }
324:
325: /**
326: * Returns a {@linkplain Warp image warp} for the specified transform. The
327: * {@link Warp#warpPoint(int,int,float[]) Warp.warpPoint} method transforms coordinates from
328: * source to target CRS. Note that JAI's {@linkplain javax.media.jai.operator.WarpDescriptor
329: * warp operation} needs a warp object with the opposite semantic (i.e. the image warp must
330: * transforms coordinates from target to source CRS). Consequently, consider invoking
331: * {@code getWarp(transform.inverse())} if the warp object is going to be used in an
332: * image reprojection.
333: *
334: * @param name The image or {@linkplain org.geotools.coverage.grid.GridCoverage2D coverage}
335: * name, or {@code null} in unknow. Used only for formatting error message if some
336: * {@link org.opengis.referencing.operation.TransformException} are thrown by the
337: * supplied transform.
338: * @param transform The transform to returns as an image warp.
339: *
340: * @todo We should check for {@link ConcatenatedTransform}. If we detect that a
341: * {@code WarpTransform2D} is concatenated with {@code AffineTransform} only, and if the
342: * {@code AffineTransform} has scale factors only, then we can ommit the {@code AffineTransform}
343: * and merge the scale factors with {@link WarpPolynomial} preScaleX, preScaleY, postScaleX and
344: * postScaleY. Additionnaly, the translation term for the post-AffineTransform may also be
345: * merged with the first coefficients of WarpPolynomial.xCoeffs and yCoeffs. See GEOT-521.
346: */
347: public static Warp getWarp(CharSequence name,
348: final MathTransform2D transform) {
349: if (transform instanceof WarpTransform2D) {
350: return ((WarpTransform2D) transform).getWarp();
351: }
352: if (name == null) {
353: name = Vocabulary
354: .formatInternational(VocabularyKeys.UNKNOW);
355: }
356: return new WarpAdapter(name, transform);
357: }
358:
359: /**
360: * Returns {@linkplain Warp image warp} wrapped by this transform. The
361: * {@link Warp#warpPoint(int,int,float[]) Warp.warpPoint} method transforms coordinates from
362: * source to target CRS. Note that JAI's {@linkplain javax.media.jai.operator.WarpDescriptor
363: * warp operation} needs a warp object with the opposite semantic (i.e. the image warp must
364: * transforms coordinates from target to source CRS). Consequently, consider invoking
365: * <code>{@linkplain #inverse}.getWarp()</code> if the warp object is going to be used in an
366: * image reprojection.
367: */
368: public Warp getWarp() {
369: return warp;
370: }
371:
372: /**
373: * Returns the parameter descriptors for this math transform.
374: */
375: public ParameterDescriptorGroup getParameterDescriptors() {
376: if (warp instanceof WarpPolynomial) {
377: return Provider.PARAMETERS;
378: } else {
379: return super .getParameterDescriptors();
380: }
381: }
382:
383: /**
384: * Returns the parameter values for this math transform.
385: */
386: public ParameterValueGroup getParameterValues() {
387: if (warp instanceof WarpPolynomial) {
388: final WarpPolynomial poly = (WarpPolynomial) warp;
389: final ParameterValue[] p = new ParameterValue[7];
390: int c = 0;
391: p[c++] = new Parameter(Provider.DEGREE, new Integer(poly
392: .getDegree()));
393: p[c++] = new Parameter(Provider.X_COEFFS, poly.getXCoeffs());
394: p[c++] = new Parameter(Provider.Y_COEFFS, poly.getYCoeffs());
395: float s;
396: if ((s = poly.getPreScaleX()) != 1)
397: p[c++] = new Parameter(Provider.PRE_SCALE_X, new Float(
398: s));
399: if ((s = poly.getPreScaleY()) != 1)
400: p[c++] = new Parameter(Provider.PRE_SCALE_Y, new Float(
401: s));
402: if ((s = poly.getPostScaleX()) != 1)
403: p[c++] = new Parameter(Provider.POST_SCALE_X,
404: new Float(s));
405: if ((s = poly.getPostScaleY()) != 1)
406: p[c++] = new Parameter(Provider.POST_SCALE_Y,
407: new Float(s));
408: return new ParameterGroup(getParameterDescriptors(),
409: (ParameterValue[]) XArray.resize(p, c));
410: } else {
411: return super .getParameterValues();
412: }
413: }
414:
415: /**
416: * Returns the dimension of input points.
417: */
418: public int getSourceDimensions() {
419: return 2;
420: }
421:
422: /**
423: * Returns the dimension of output points.
424: */
425: public int getTargetDimensions() {
426: return 2;
427: }
428:
429: /**
430: * Tests if this transform is the identity transform.
431: */
432: public boolean isIdentity() {
433: return false;
434: }
435:
436: /**
437: * Transforms source coordinates (usually pixel indices) into destination coordinates
438: * (usually "real world" coordinates).
439: *
440: * @param ptSrc the specified coordinate point to be transformed.
441: * @param ptDst the specified coordinate point that stores the result of transforming
442: * {@code ptSrc}, or {@code null}.
443: * @return the coordinate point after transforming {@code ptSrc} and storing the result in
444: * {@code ptDst}.
445: */
446: public Point2D transform(Point2D ptSrc, Point2D ptDst) {
447: /*
448: * We have to copy the coordinate in a temporary point object because we don't know
449: * neither the ptSrc or ptDst type. Since mapDestPoint returns a clone of the point
450: * given in argument, giving ptSrc directly would result in a lost of precision if
451: * its type is java.awt.Point (for example), even if ptDst had a greater precision.
452: *
453: * There is also an other reason for creating a temporary object:
454: * JAI's Warp is designed for mapping pixel coordinates in J2SE's image. In JAI, pixel
455: * coordinates map by definition to the pixel's upper left corner. But for interpolation
456: * purpose, JAI needs to map pixel's center. This introduce a shift of 0.5, which is
457: * documented (for example) in WarpAffine.mapDestPoint(Point2D).
458: */
459: ptSrc = new PointDouble(ptSrc.getX() - 0.5, ptSrc.getY() - 0.5);
460: final Point2D result = warp.mapDestPoint(ptSrc);
461: result.setLocation(result.getX() + 0.5, result.getY() + 0.5);
462: if (ptDst == null) {
463: // Do not returns 'result' directly, since it has tricked 'clone()' method.
464: ptDst = new Point2D.Float();
465: }
466: ptDst.setLocation(result);
467: return ptDst;
468: }
469:
470: /**
471: * Transforms source coordinates (usually pixel indices) into destination coordinates
472: * (usually "real world" coordinates).
473: */
474: public void transform(final float[] srcPts, int srcOff,
475: final float[] dstPts, int dstOff, int numPts) {
476: final int postIncrement;
477: if (srcPts == dstPts && srcOff < dstOff) {
478: srcOff += (numPts - 1) * 2;
479: dstOff += (numPts - 1) * 2;
480: postIncrement = -4;
481: } else {
482: postIncrement = 0;
483: }
484: final Point2D.Float ptSrc = new PointFloat();
485: final float[] ptDst = new float[2];
486: while (--numPts >= 0) {
487: ptSrc.x = srcPts[srcOff++] - 0.5f; // See the comment in transform(Point2D...)
488: ptSrc.y = srcPts[srcOff++] - 0.5f; // for an explanation about the 0.5 shift.
489: final Point2D result = warp.mapDestPoint(ptSrc);
490: dstPts[dstOff++] = (float) (result.getX() + 0.5);
491: dstPts[dstOff++] = (float) (result.getY() + 0.5);
492: dstOff += postIncrement;
493: }
494: }
495:
496: /**
497: * Transforms source coordinates (usually pixel indices) into destination coordinates
498: * (usually "real world" coordinates).
499: */
500: public void transform(final double[] srcPts, int srcOff,
501: final double[] dstPts, int dstOff, int numPts) {
502: final int postIncrement;
503: if (srcPts == dstPts && srcOff < dstOff) {
504: srcOff += (numPts - 1) * 2;
505: dstOff += (numPts - 1) * 2;
506: postIncrement = -4;
507: } else {
508: postIncrement = 0;
509: }
510: final Point2D.Double ptSrc = new PointDouble();
511: final float[] ptDst = new float[2];
512: while (--numPts >= 0) {
513: ptSrc.x = srcPts[srcOff++] - 0.5; // See the comment in transform(Point2D...)
514: ptSrc.y = srcPts[srcOff++] - 0.5; // for an explanation about the 0.5 shift.
515: final Point2D result = warp.mapDestPoint(ptSrc);
516: dstPts[dstOff++] = result.getX() + 0.5;
517: dstPts[dstOff++] = result.getY() + 0.5;
518: dstOff += postIncrement;
519: }
520: }
521:
522: /**
523: * Returns the inverse transform.
524: *
525: * @throws NoninvertibleTransformException if no inverse warp were specified at construction time.
526: */
527: public MathTransform inverse()
528: throws NoninvertibleTransformException {
529: if (inverse != null) {
530: return inverse;
531: } else {
532: return super .inverse();
533: }
534: }
535:
536: /**
537: * Returns a hash value for this transform.
538: */
539: public int hashCode() {
540: return (int) serialVersionUID ^ super .hashCode()
541: ^ warp.hashCode();
542: }
543:
544: /**
545: * Compares this transform with the specified object for equality.
546: */
547: public boolean equals(final Object object) {
548: if (super .equals(object)) {
549: final WarpTransform2D that = (WarpTransform2D) object;
550: return Utilities.equals(this .warp, that.warp);
551: }
552: return false;
553: }
554:
555: /**
556: * A {@code Point2D.Float} that returns itself when {@link #clone} is invoked.
557: * This trick is used for avoiding the creation of thousands of temporary objects
558: * when transforming an array of points using {@link Warp#mapDestPoint}.
559: */
560: private static final class PointFloat extends Point2D.Float {
561: public Object clone() {
562: return this ;
563: }
564: }
565:
566: /**
567: * A {@code Point2D.Double} that returns itself when {@link #clone} is invoked.
568: * This trick is used for avoiding the creation of thousands of temporary objects
569: * when transforming an array of points using {@link Warp#mapDestPoint}.
570: */
571: private static final class PointDouble extends Point2D.Double {
572: public PointDouble() {
573: super ();
574: }
575:
576: public PointDouble(double x, double y) {
577: super (x, y);
578: }
579:
580: public Object clone() {
581: return this ;
582: }
583: }
584:
585: /**
586: * The provider for the {@link WarpTransform2D}. This provider constructs a JAI
587: * {@linkplain WarpPolynomial image warp} from a set of polynomial coefficients,
588: * and wrap it in a {@link WarpTransform2D} object.
589: *
590: * @version $Id: WarpTransform2D.java 29101 2008-02-06 12:11:19Z desruisseaux $
591: * @author Martin Desruisseaux
592: */
593: public static class Provider extends MathTransformProvider {
594: /** Serial number for interoperability with different versions. */
595: private static final long serialVersionUID = -7949539694656719923L;
596:
597: /** Descriptor for the "{@link WarpPolynomial#getDegree degree}" parameter value. */
598: public static final ParameterDescriptor DEGREE = new DefaultParameterDescriptor(
599: "degree", 2, 1, MAX_DEGREE);
600:
601: /** Descriptor for the "{@link WarpPolynomial#getXCoeffs xCoeffs}" parameter value. */
602: public static final ParameterDescriptor X_COEFFS = new DefaultParameterDescriptor(
603: "xCoeffs", float[].class, null, null);
604:
605: /** Descriptor for the "{@link WarpPolynomial#getYCoeffs yCoeffs}" parameter value. */
606: public static final ParameterDescriptor Y_COEFFS = new DefaultParameterDescriptor(
607: "yCoeffs", float[].class, null, null);
608:
609: /** Descriptor for the "{@link WarpPolynomial#getPreScaleX preScaleX}" parameter value. */
610: public static final ParameterDescriptor PRE_SCALE_X;
611:
612: /** Descriptor for the "{@link WarpPolynomial#getPreScaleY preScaleY}" parameter value. */
613: public static final ParameterDescriptor PRE_SCALE_Y;
614:
615: /** Descriptor for the "{@link WarpPolynomial#getPostScaleX postScaleX}" parameter value. */
616: public static final ParameterDescriptor POST_SCALE_X;
617:
618: /** Descriptor for the "{@link WarpPolynomial#getPostScaleY postScaleY}" parameter value. */
619: public static final ParameterDescriptor POST_SCALE_Y;
620: static {
621: final Float ONE = new Float(1);
622: PRE_SCALE_X = new DefaultParameterDescriptor("preScaleX",
623: null, ONE, false);
624: PRE_SCALE_Y = new DefaultParameterDescriptor("preScaleY",
625: null, ONE, false);
626: POST_SCALE_X = new DefaultParameterDescriptor("postScaleX",
627: null, ONE, false);
628: POST_SCALE_Y = new DefaultParameterDescriptor("postScaleY",
629: null, ONE, false);
630: }
631:
632: /**
633: * The parameters group.
634: */
635: static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
636: new NamedIdentifier[] { new NamedIdentifier(
637: Citations.GEOTOOLS, "WarpPolynomial") },
638: new ParameterDescriptor[] { DEGREE, X_COEFFS, Y_COEFFS,
639: PRE_SCALE_X, PRE_SCALE_Y, POST_SCALE_X,
640: POST_SCALE_Y });
641:
642: /**
643: * Create a provider for warp transforms.
644: */
645: public Provider() {
646: super (2, 2, PARAMETERS);
647: }
648:
649: /**
650: * Returns the operation type.
651: */
652: public Class getOperationType() {
653: return Transformation.class;
654: }
655:
656: /**
657: * Creates a warp transform from the specified group of parameter values.
658: *
659: * @param values The group of parameter values.
660: * @return The created math transform.
661: * @throws ParameterNotFoundException if a required parameter was not found.
662: */
663: protected MathTransform createMathTransform(
664: final ParameterValueGroup values)
665: throws ParameterNotFoundException {
666: final int degree = intValue(DEGREE, values);
667: final float[] xCoeffs = (float[]) value(X_COEFFS, values);
668: final float[] yCoeffs = (float[]) value(Y_COEFFS, values);
669: final float preScaleX = scale(PRE_SCALE_X, values);
670: final float preScaleY = scale(PRE_SCALE_Y, values);
671: final float postScaleX = scale(POST_SCALE_X, values);
672: final float postScaleY = scale(POST_SCALE_Y, values);
673: final Warp warp;
674: switch (degree) {
675: case 1:
676: warp = new WarpAffine(xCoeffs, yCoeffs, preScaleX,
677: preScaleY, postScaleX, postScaleY);
678: break;
679: case 2:
680: warp = new WarpQuadratic(xCoeffs, yCoeffs, preScaleX,
681: preScaleY, postScaleX, postScaleY);
682: break;
683: case 3:
684: warp = new WarpCubic(xCoeffs, yCoeffs, preScaleX,
685: preScaleY, postScaleX, postScaleY);
686: break;
687: default:
688: warp = new WarpGeneralPolynomial(xCoeffs, yCoeffs,
689: preScaleX, preScaleY, postScaleX, postScaleY);
690: break;
691: }
692: return new WarpTransform2D(warp, (Warp) null);
693: }
694:
695: /**
696: * Returns the parameter value for the specified operation parameter.
697: *
698: * @param param The parameter to look for.
699: * @param group The parameter value group to search into.
700: * @return The requested parameter value, or {@code 1} if none.
701: */
702: private static float scale(final ParameterDescriptor param,
703: final ParameterValueGroup group)
704: throws ParameterNotFoundException {
705: final Object value = value(param, group);
706: return (value != null) ? ((Number) value).floatValue() : 1;
707: }
708: }
709: }
|