001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
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.matrix;
018:
019: // J2SE dependencies
020: import java.awt.geom.AffineTransform;
021: import java.awt.geom.NoninvertibleTransformException;
022: import java.awt.geom.Point2D;
023: import java.awt.geom.Rectangle2D;
024:
025: // Geotools dependencies
026: import org.geotools.resources.XMath;
027:
028: /**
029: * Utility methods for affine transforms. This class provides two kind of services:
030: *
031: * <ul>
032: * <li><p>A set of public static methods working on any {@link AffineTransform}.</p></li>
033: * <li><p>An abstract base class that override all mutable {@link AffineTransform} methods
034: * in order to check for permission before changing the transform's state.
035: * If {@link #checkPermission} is defined to always throw an exception,
036: * then {@code XAffineTransform} is immutable.</p></li>
037: * </ul>
038: *
039: * @since 2.3
040: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/matrix/XAffineTransform.java $
041: * @version $Id: XAffineTransform.java 26030 2007-06-26 14:55:47Z desruisseaux $
042: * @author Martin Desruisseaux
043: * @author Simone Giannecchini
044: */
045: public abstract class XAffineTransform extends AffineTransform {
046: /**
047: * Serial number for interoperability with different versions.
048: */
049: private static final long serialVersionUID = 5215291166450556451L;
050:
051: /**
052: * Tolerance value for floating point comparisons.
053: *
054: * @deprecated To be removed after we removed the deprecated {@link #round(AffineTransform)}
055: * method (replaced by {@link #round(AffineTransform,double)}).
056: */
057: public static final double EPS = 1E-6;
058:
059: /**
060: * Constructs a new {@code XAffineTransform} that is a
061: * copy of the specified {@code AffineTransform} object.
062: */
063: protected XAffineTransform(final AffineTransform tr) {
064: super (tr);
065: }
066:
067: /**
068: * Check if the caller is allowed to change this {@code XAffineTransform} state.
069: * If this method is defined to thrown an exception in all case, then this
070: * {@code XAffineTransform} is immutable.
071: */
072: protected abstract void checkPermission();
073:
074: /**
075: * Check for {@linkplain #checkPermission permission} before translating this transform.
076: */
077: public void translate(double tx, double ty) {
078: checkPermission();
079: super .translate(tx, ty);
080: }
081:
082: /**
083: * Check for {@linkplain #checkPermission permission} before rotating this transform.
084: */
085: public void rotate(double theta) {
086: checkPermission();
087: super .rotate(theta);
088: }
089:
090: /**
091: * Check for {@linkplain #checkPermission permission} before rotating this transform.
092: */
093: public void rotate(double theta, double x, double y) {
094: checkPermission();
095: super .rotate(theta, x, y);
096: }
097:
098: /**
099: * Check for {@linkplain #checkPermission permission} before scaling this transform.
100: */
101: public void scale(double sx, double sy) {
102: checkPermission();
103: super .scale(sx, sy);
104: }
105:
106: /**
107: * Check for {@linkplain #checkPermission permission} before shearing this transform.
108: */
109: public void shear(double shx, double shy) {
110: checkPermission();
111: super .shear(shx, shy);
112: }
113:
114: /**
115: * Check for {@linkplain #checkPermission permission} before setting this transform.
116: */
117: public void setToIdentity() {
118: checkPermission();
119: super .setToIdentity();
120: }
121:
122: /**
123: * Check for {@linkplain #checkPermission permission} before setting this transform.
124: */
125: public void setToTranslation(double tx, double ty) {
126: checkPermission();
127: super .setToTranslation(tx, ty);
128: }
129:
130: /**
131: * Check for {@linkplain #checkPermission permission} before setting this transform.
132: */
133: public void setToRotation(double theta) {
134: checkPermission();
135: super .setToRotation(theta);
136: }
137:
138: /**
139: * Check for {@linkplain #checkPermission permission} before setting this transform.
140: */
141: public void setToRotation(double theta, double x, double y) {
142: checkPermission();
143: super .setToRotation(theta, x, y);
144: }
145:
146: /**
147: * Check for {@linkplain #checkPermission permission} before setting this transform.
148: */
149: public void setToScale(double sx, double sy) {
150: checkPermission();
151: super .setToScale(sx, sy);
152: }
153:
154: /**
155: * Check for {@linkplain #checkPermission permission} before setting this transform.
156: */
157: public void setToShear(double shx, double shy) {
158: checkPermission();
159: super .setToShear(shx, shy);
160: }
161:
162: /**
163: * Check for {@linkplain #checkPermission permission} before setting this transform.
164: */
165: public void setTransform(AffineTransform Tx) {
166: checkPermission();
167: super .setTransform(Tx);
168: }
169:
170: /**
171: * Check for {@linkplain #checkPermission permission} before setting this transform.
172: */
173: public void setTransform(double m00, double m10, double m01,
174: double m11, double m02, double m12) {
175: checkPermission();
176: super .setTransform(m00, m10, m01, m11, m02, m12);
177: }
178:
179: /**
180: * Check for {@linkplain #checkPermission permission} before concatenating this transform.
181: */
182: public void concatenate(AffineTransform Tx) {
183: checkPermission();
184: super .concatenate(Tx);
185: }
186:
187: /**
188: * Check for {@linkplain #checkPermission permission} before concatenating this transform.
189: */
190: public void preConcatenate(AffineTransform Tx) {
191: checkPermission();
192: super .preConcatenate(Tx);
193: }
194:
195: /**
196: * Check whether or not this {@code XAffineTransform} is the identity by
197: * using the provided {@code tolerance}.
198: *
199: * @param tolerance The tolerance to use for this check.
200: * @return {@code true} if the transform is identity, {@code false} otherwise.
201: *
202: * @since 2.3.1
203: */
204: public boolean isIdentity(double tolerance) {
205: return isIdentity(this , tolerance);
206: }
207:
208: /**
209: * Returns {@code true} if the specified affine transform is an identity transform up to the
210: * specified tolerance. This method is equivalent to computing the difference between this
211: * matrix and an identity matrix (as created by {@link AffineTransform#AffineTransform()
212: * new AffineTransform()}) and returning {@code true} if and only if all differences are
213: * smaller than or equal to {@code tolerance}.
214: * <p>
215: * This method is used for working around rounding error in affine transforms resulting
216: * from a computation, as in the example below:
217: *
218: * <blockquote><pre>
219: * [ 1.0000000000000000001 0.0 0.0 ]
220: * [ 0.0 0.999999999999999999999 0.0 ]
221: * [ 0.0 0.0 1.0 ]
222: * </pre></blockquote>
223: *
224: * @param tr The affine transform to be checked for identity.
225: * @param tolerance The tolerance value to use when checking for identity.
226: * return {@code true} if this tranformation is close enough to the
227: * identity, {@code false} otherwise.
228: *
229: * @since 2.3.1
230: */
231: public static boolean isIdentity(final AffineTransform tr,
232: double tolerance) {
233: if (tr.isIdentity()) {
234: return true;
235: }
236: tolerance = Math.abs(tolerance);
237: return Math.abs(tr.getScaleX() - 1) <= tolerance
238: && Math.abs(tr.getScaleY() - 1) <= tolerance
239: && Math.abs(tr.getShearX()) <= tolerance
240: && Math.abs(tr.getShearY()) <= tolerance
241: && Math.abs(tr.getTranslateX()) <= tolerance
242: && Math.abs(tr.getTranslateY()) <= tolerance;
243: }
244:
245: /**
246: * Returns a rectangle which entirely contains the direct
247: * transform of {@code bounds}. This operation is equivalent to:
248: *
249: * <blockquote><code>
250: * {@linkplain #createTransformedShape createTransformedShape}(bounds).{@linkplain
251: * Rectangle2D#getBounds2D() getBounds2D()}
252: * </code></blockquote>
253: *
254: * @param transform Affine transform to use.
255: * @param bounds Rectangle to transform. This rectangle will not be modified.
256: * @param dest Rectangle in which to place the result. If null, a new
257: * rectangle will be created.
258: *
259: * @return The direct transform of the {@code bounds} rectangle.
260: */
261: public static Rectangle2D transform(
262: final AffineTransform transform, final Rectangle2D bounds,
263: final Rectangle2D dest) {
264: double xmin = Double.POSITIVE_INFINITY;
265: double ymin = Double.POSITIVE_INFINITY;
266: double xmax = Double.NEGATIVE_INFINITY;
267: double ymax = Double.NEGATIVE_INFINITY;
268: final Point2D.Double point = new Point2D.Double();
269: for (int i = 0; i < 4; i++) {
270: point.x = (i & 1) == 0 ? bounds.getMinX() : bounds
271: .getMaxX();
272: point.y = (i & 2) == 0 ? bounds.getMinY() : bounds
273: .getMaxY();
274: transform.transform(point, point);
275: if (point.x < xmin)
276: xmin = point.x;
277: if (point.x > xmax)
278: xmax = point.x;
279: if (point.y < ymin)
280: ymin = point.y;
281: if (point.y > ymax)
282: ymax = point.y;
283: }
284: if (dest != null) {
285: dest.setRect(xmin, ymin, xmax - xmin, ymax - ymin);
286: return dest;
287: }
288: return new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax
289: - ymin);
290: }
291:
292: /**
293: * Returns a rectangle which entirely contains the inverse
294: * transform of {@code bounds}. This operation is equivalent to:
295: *
296: * <blockquote><code>
297: * {@linkplain #createInverse() createInverse()}.{@linkplain
298: * #createTransformedShape createTransformedShape}(bounds).{@linkplain
299: * Rectangle2D#getBounds2D() getBounds2D()}
300: * </code></blockquote>
301: *
302: * @param transform Affine transform to use.
303: * @param bounds Rectangle to transform. This rectangle will not be modified.
304: * @param dest Rectangle in which to place the result. If null, a new
305: * rectangle will be created.
306: *
307: * @return The inverse transform of the {@code bounds} rectangle.
308: * @throws NoninvertibleTransformException if the affine transform can't be inverted.
309: */
310: public static Rectangle2D inverseTransform(
311: final AffineTransform transform, final Rectangle2D bounds,
312: final Rectangle2D dest)
313: throws NoninvertibleTransformException {
314: double xmin = Double.POSITIVE_INFINITY;
315: double ymin = Double.POSITIVE_INFINITY;
316: double xmax = Double.NEGATIVE_INFINITY;
317: double ymax = Double.NEGATIVE_INFINITY;
318: final Point2D.Double point = new Point2D.Double();
319: for (int i = 0; i < 4; i++) {
320: point.x = (i & 1) == 0 ? bounds.getMinX() : bounds
321: .getMaxX();
322: point.y = (i & 2) == 0 ? bounds.getMinY() : bounds
323: .getMaxY();
324: transform.inverseTransform(point, point);
325: if (point.x < xmin)
326: xmin = point.x;
327: if (point.x > xmax)
328: xmax = point.x;
329: if (point.y < ymin)
330: ymin = point.y;
331: if (point.y > ymax)
332: ymax = point.y;
333: }
334: if (dest != null) {
335: dest.setRect(xmin, ymin, xmax - xmin, ymax - ymin);
336: return dest;
337: }
338: return new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax
339: - ymin);
340: }
341:
342: /**
343: * Calculates the inverse affine transform of a point without without
344: * applying the translation components.
345: *
346: * @param transform Affine transform to use.
347: * @param source Point to transform. This rectangle will not be modified.
348: * @param dest Point in which to place the result. If {@code null}, a
349: * new point will be created.
350: *
351: * @return The inverse transform of the {@code source} point.
352: * @throws NoninvertibleTransformException if the affine transform can't be inverted.
353: */
354: public static Point2D inverseDeltaTransform(
355: final AffineTransform transform, final Point2D source,
356: final Point2D dest) throws NoninvertibleTransformException {
357: final double m00 = transform.getScaleX();
358: final double m11 = transform.getScaleY();
359: final double m01 = transform.getShearX();
360: final double m10 = transform.getShearY();
361: final double det = m00 * m11 - m01 * m10;
362: if (!(Math.abs(det) > Double.MIN_VALUE)) {
363: return transform.createInverse().deltaTransform(source,
364: dest);
365: }
366: final double x0 = source.getX();
367: final double y0 = source.getY();
368: final double x = (x0 * m11 - y0 * m01) / det;
369: final double y = (y0 * m00 - x0 * m10) / det;
370: if (dest != null) {
371: dest.setLocation(x, y);
372: return dest;
373: }
374: return new Point2D.Double(x, y);
375: }
376:
377: /**
378: * Returns an estimation about whatever the specified transform swaps <var>x</var>
379: * and <var>y</var> axis. This method assumes that the specified affine transform
380: * is built from arbitrary translations, scales or rotations, but no shear. It
381: * returns {@code +1} if the (<var>x</var>, <var>y</var>) axis order seems to be
382: * preserved, {@code -1} if the transform seems to swap axis to the (<var>y</var>,
383: * <var>x</var>) axis order, or {@code 0} if this method can not make a decision.
384: */
385: public static int getSwapXY(final AffineTransform tr) {
386: final int flip = getFlip(tr);
387: if (flip != 0) {
388: final double scaleX = getScaleX0(tr);
389: final double scaleY = getScaleY0(tr) * flip;
390: final double y = Math.abs(tr.getShearY() / scaleY
391: - tr.getShearX() / scaleX);
392: final double x = Math.abs(tr.getScaleY() / scaleY
393: + tr.getScaleX() / scaleX);
394: if (x > y)
395: return +1;
396: if (x < y)
397: return -1;
398: // At this point, we may have (x == y) or some NaN value.
399: }
400: return 0;
401: }
402:
403: /**
404: * Returns an estimation of the rotation angle in radians. This method assumes that the
405: * specified affine transform is built from arbitrary translations, scales or rotations,
406: * but no shear. If a flip has been applied, then this method assumes that the flipped
407: * axis is the <var>y</var> one in <cite>source CRS</cite> space. For a <cite>grid to
408: * world CRS</cite> transform, this is the row number in grid coordinates.
409: *
410: * @param tr The affine transform to inspect.
411: * @return An estimation of the rotation angle in radians, or {@link Double#NaN NaN}
412: * if the angle can not be estimated.
413: */
414: public static double getRotation(final AffineTransform tr) {
415: final int flip = getFlip(tr);
416: if (flip != 0) {
417: final double scaleX = getScaleX0(tr);
418: final double scaleY = getScaleY0(tr) * flip;
419: return Math.atan2(tr.getShearY() / scaleY - tr.getShearX()
420: / scaleX, tr.getScaleY() / scaleY + tr.getScaleX()
421: / scaleX);
422: }
423: return Double.NaN;
424: }
425:
426: /**
427: * Returns {@code -1} if one axis has been flipped, {@code +1} if no axis has been flipped,
428: * or 0 if unknown. A flipped axis in an axis with direction reversed (typically the
429: * <var>y</var> axis). This method assumes that the specified affine transform is built
430: * from arbitrary translations, scales or rotations, but no shear. Note that it is not
431: * possible to determine which of the <var>x</var> or <var>y</var> axis has been flipped.
432: * <p>
433: * This method can be used in order to set the sign of a scale according the flipping state.
434: * The example below choose to apply the sign on the <var>y</var> scale, but this is an
435: * arbitrary (while common) choice:
436: *
437: * <blockquote><code>
438: * double scaleX0 = getScaleX0(transform);
439: * double scaleY0 = getScaleY0(transform);
440: * int flip = getFlip(transform);
441: * if (flip != 0) {
442: * scaleY0 *= flip;
443: * // ... continue the process here.
444: * }
445: * </code></blockquote>
446: *
447: * This method is similar to the following code, except that this method
448: * distinguish between "unflipped" and "unknow" states.
449: *
450: * <blockquote><code>
451: * boolean flipped = (tr.{@linkplain #getType() getType()} & {@linkplain #TYPE_FLIP}) != 0;
452: * </code></blockquote>
453: */
454: public static int getFlip(final AffineTransform tr) {
455: final int scaleX = XMath.sgn(tr.getScaleX());
456: final int scaleY = XMath.sgn(tr.getScaleY());
457: final int shearX = XMath.sgn(tr.getShearX());
458: final int shearY = XMath.sgn(tr.getShearY());
459: if (scaleX == scaleY && shearX == -shearY)
460: return +1;
461: if (scaleX == -scaleY && shearX == shearY)
462: return -1;
463: return 0;
464: }
465:
466: /**
467: * Returns the magnitude of scale factor <var>x</var> by cancelling the
468: * effect of eventual flip and rotation. This factor is calculated by
469: * <IMG src="{@docRoot}/org/geotools/display/canvas/doc-files/scaleX0.png">.
470: */
471: public static double getScaleX0(final AffineTransform tr) {
472: return XMath.hypot(tr.getScaleX(), tr.getShearX());
473: }
474:
475: /**
476: * Returns the magnitude of scale factor <var>y</var> by cancelling the
477: * effect of eventual flip and rotation. This factor is calculated by
478: * <IMG src="{@docRoot}/org/geotools/display/canvas/doc-files/scaleY0.png">.
479: */
480: public static double getScaleY0(final AffineTransform tr) {
481: return XMath.hypot(tr.getScaleY(), tr.getShearY());
482: }
483:
484: /**
485: * Returns a global scale factor for the specified affine transform.
486: * This scale factor will combines {@link #getScaleX0} and {@link #getScaleY0}.
487: * The way to compute such a "global" scale is somewhat arbitrary and may change
488: * in a future version.
489: */
490: public static double getScale(final AffineTransform tr) {
491: return 0.5 * (getScaleX0(tr) + getScaleY0(tr));
492: }
493:
494: /**
495: * Returns an affine transform representing a zoom carried out around a
496: * central point (<var>x</var>, <var>y</var>). The transforms will leave
497: * the specified (<var>x</var>, <var>y</var>) coordinate unchanged.
498: *
499: * @param sx Scale along <var>x</var> axis.
500: * @param sy Scale along <var>y</var> axis.
501: * @param x <var>x</var> coordinates of the central point.
502: * @param y <var>y</var> coordinates of the central point.
503: * @return Affine transform of a zoom which leaves the
504: * (<var>x</var>,<var>y</var>) coordinate unchanged.
505: */
506: public static AffineTransform getScaleInstance(final double sx,
507: final double sy, final double x, final double y) {
508: return new AffineTransform(sx, 0, 0, sy, (1 - sx) * x, (1 - sy)
509: * y);
510: }
511:
512: /**
513: * Checks whether the matrix coefficients are close to whole numbers.
514: * If this is the case, these coefficients will be rounded up to the
515: * nearest whole numbers. This rounding up is useful, for example, for
516: * speeding up image displays. Above all, it is efficient when we know that
517: * a matrix has a chance of being close to the similarity matrix.
518: * <p>
519: * It is crucial to note that this method uses a default rounding threshold
520: * whose value is held by the field {@link #EPS} which is {@value #EPS}.
521: *
522: * @deprecated Use {@link #round(AffineTransform, double)} instead.
523: */
524: public static void round(final AffineTransform tr) {
525: round(tr, EPS);
526: }
527:
528: /**
529: * Checks whether the matrix coefficients are close to whole numbers.
530: * If this is the case, these coefficients will be rounded up to the
531: * nearest whole numbers. This rounding up is useful, for example, for
532: * speeding up image displays. Above all, it is efficient when we know that
533: * a matrix has a chance of being close to the similarity matrix.
534: *
535: * @param tr The matrix to round. Rounding will be applied in place.
536: * @param tolerance The maximal departure from integers in order to allow rounding.
537: * It is typically a small number like {@code 1E-6}.
538: *
539: * @since 2.3.1
540: */
541: public static void round(final AffineTransform tr,
542: final double tolerance) {
543: double r;
544: final double m00, m01, m10, m11;
545: if (Math.abs((m00 = Math.rint(r = tr.getScaleX())) - r) <= tolerance
546: && Math.abs((m01 = Math.rint(r = tr.getShearX())) - r) <= tolerance
547: && Math.abs((m11 = Math.rint(r = tr.getScaleY())) - r) <= tolerance
548: && Math.abs((m10 = Math.rint(r = tr.getShearY())) - r) <= tolerance) {
549: if ((m00 != 0 || m01 != 0) && (m10 != 0 || m11 != 0)) {
550: double m02 = Math.rint(r = tr.getTranslateX());
551: if (!(Math.abs(m02 - r) <= tolerance))
552: m02 = r;
553: double m12 = Math.rint(r = tr.getTranslateY());
554: if (!(Math.abs(m12 - r) <= tolerance))
555: m12 = r;
556: tr.setTransform(m00, m10, m01, m11, m02, m12);
557: }
558: }
559: }
560: }
|