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: * This package contains documentation from OpenGIS specifications.
019: * OpenGIS consortium's work is fully acknowledged here.
020: */
021: package org.geotools.referencing.operation.transform;
022:
023: // J2SE and vecmath dependencies
024: import java.io.Serializable;
025: import java.awt.Shape;
026: import java.awt.geom.Point2D;
027: import java.awt.geom.GeneralPath;
028: import java.awt.geom.PathIterator;
029: import java.awt.geom.AffineTransform;
030: import java.awt.geom.IllegalPathStateException;
031: import javax.vecmath.SingularMatrixException;
032: import javax.units.NonSI;
033: import javax.units.SI;
034:
035: // OpenGIS dependencies
036: import org.opengis.referencing.operation.Matrix;
037: import org.opengis.referencing.operation.MathTransform;
038: import org.opengis.referencing.operation.MathTransform1D;
039: import org.opengis.referencing.operation.MathTransform2D;
040: import org.opengis.referencing.operation.Operation;
041: import org.opengis.referencing.operation.OperationMethod;
042: import org.opengis.referencing.operation.TransformException;
043: import org.opengis.referencing.operation.NoninvertibleTransformException;
044: import org.opengis.geometry.MismatchedDimensionException;
045: import org.opengis.geometry.DirectPosition;
046: import org.opengis.parameter.InvalidParameterValueException;
047: import org.opengis.parameter.ParameterDescriptorGroup;
048: import org.opengis.parameter.ParameterValueGroup;
049:
050: // Geotools dependencies
051: import org.geotools.referencing.wkt.Formatter;
052: import org.geotools.referencing.wkt.Formattable;
053: import org.geotools.referencing.operation.matrix.XMatrix;
054: import org.geotools.referencing.operation.matrix.Matrix1;
055: import org.geotools.referencing.operation.matrix.GeneralMatrix;
056: import org.geotools.referencing.operation.matrix.MatrixFactory;
057: import org.geotools.geometry.GeneralDirectPosition;
058: import org.geotools.resources.geometry.ShapeUtilities;
059: import org.geotools.resources.Utilities;
060: import org.geotools.resources.i18n.Errors;
061: import org.geotools.resources.i18n.ErrorKeys;
062:
063: /**
064: * Provides a default implementation for most methods required by the
065: * {@link MathTransform} interface. {@code AbstractMathTransform}
066: * provides a convenient base class from which other transform classes
067: * can be easily derived. In addition, {@code AbstractMathTransform}
068: * implements methods required by the {@link MathTransform2D} interface,
069: * but <strong>does not</strong> implements {@code MathTransform2D}.
070: * Subclasses must declare <code>implements MathTransform2D</code>
071: * themself if they know to maps two-dimensional coordinate systems.
072: *
073: * @since 2.0
074: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/AbstractMathTransform.java $
075: * @version $Id: AbstractMathTransform.java 24925 2007-03-27 20:12:08Z jgarnett $
076: * @author Martin Desruisseaux
077: *
078: * @tutorial http://docs.codehaus.org/display/GEOTOOLS/Coordinate+Transformation+Parameters
079: */
080: public abstract class AbstractMathTransform extends Formattable
081: implements MathTransform {
082: /**
083: * Constructs a math transform.
084: */
085: protected AbstractMathTransform() {
086: }
087:
088: /**
089: * Gets the dimension of input points.
090: */
091: public abstract int getSourceDimensions();
092:
093: /**
094: * Gets the dimension of output points.
095: */
096: public abstract int getTargetDimensions();
097:
098: /**
099: * Returns the parameter descriptors for this math transform, or {@code null} if unknow.
100: * This method is similar to {@link OperationMethod#getParameters}, except that
101: * {@code MathTransform} returns parameters in standard units (usually
102: * {@linkplain SI#METER meters} or {@linkplain NonSI#DEGREE_ANGLE decimal degrees}).
103: *
104: * @return The parameter descriptors for this math transform, or {@code null}.
105: *
106: * @see OperationMethod#getParameters
107: */
108: public ParameterDescriptorGroup getParameterDescriptors() {
109: return null;
110: }
111:
112: /**
113: * Returns the parameter values for this math transform, or {@code null} if unknow.
114: * This method is similar to {@link Operation#getParameterValues}, except that
115: * {@code MathTransform} returns parameters in standard units (usually
116: * {@linkplain SI#METER meters} or {@linkplain NonSI#DEGREE_ANGLE decimal degrees}).
117: * Since this method returns a copy of the parameter values, any change to a value
118: * will have no effect on this math transform.
119: *
120: * @return A copy of the parameter values for this math transform, or {@code null}.
121: *
122: * @see Operation#getParameterValues
123: */
124: public ParameterValueGroup getParameterValues() {
125: return null;
126: }
127:
128: /**
129: * Tests whether this transform does not move any points.
130: * The default implementation always returns {@code false}.
131: */
132: public boolean isIdentity() {
133: return false;
134: }
135:
136: /**
137: * Constructs an error message for the {@link MismatchedDimensionException}.
138: *
139: * @param argument The argument name with the wrong number of dimensions.
140: * @param dimension The wrong dimension.
141: * @param expected The expected dimension.
142: */
143: private static String constructMessage(final String argument,
144: final int dimension, final int expected) {
145: return Errors
146: .format(ErrorKeys.MISMATCHED_DIMENSION_$3, argument,
147: new Integer(dimension), new Integer(expected));
148: }
149:
150: /**
151: * Transforms the specified {@code ptSrc} and stores the result in {@code ptDst}.
152: * The default implementation invokes {@link #transform(double[],int,double[],int,int)}
153: * using a temporary array of doubles.
154: *
155: * @param ptSrc the specified coordinate point to be transformed.
156: * @param ptDst the specified coordinate point that stores the result of transforming
157: * {@code ptSrc}, or {@code null}.
158: * @return the coordinate point after transforming {@code ptSrc} and storing the result in
159: * {@code ptDst}.
160: * @throws MismatchedDimensionException if this transform doesn't map two-dimensional
161: * coordinate systems.
162: * @throws TransformException if the point can't be transformed.
163: *
164: * @see MathTransform2D#transform(Point2D,Point2D)
165: */
166: public Point2D transform(final Point2D ptSrc, final Point2D ptDst)
167: throws TransformException {
168: int dim;
169: if ((dim = getSourceDimensions()) != 2) {
170: throw new MismatchedDimensionException(constructMessage(
171: "ptSrc", 2, dim));
172: }
173: if ((dim = getTargetDimensions()) != 2) {
174: throw new MismatchedDimensionException(constructMessage(
175: "ptDst", 2, dim));
176: }
177: final double[] ord = new double[] { ptSrc.getX(), ptSrc.getY() };
178: this .transform(ord, 0, ord, 0, 1);
179: if (ptDst != null) {
180: ptDst.setLocation(ord[0], ord[1]);
181: return ptDst;
182: } else {
183: return new Point2D.Double(ord[0], ord[1]);
184: }
185: }
186:
187: /**
188: * Transforms the specified {@code ptSrc} and stores the result
189: * in {@code ptDst}. The default implementation invokes
190: * {@link #transform(double[],int,double[],int,int)}.
191: */
192: public DirectPosition transform(final DirectPosition ptSrc,
193: DirectPosition ptDst) throws TransformException {
194: int dimPoint = ptSrc.getDimension();
195: final int dimSource = getSourceDimensions();
196: final int dimTarget = getTargetDimensions();
197: if (dimPoint != dimSource) {
198: throw new MismatchedDimensionException(constructMessage(
199: "ptSrc", dimPoint, dimSource));
200: }
201: if (ptDst != null) {
202: dimPoint = ptDst.getDimension();
203: if (dimPoint != dimTarget) {
204: throw new MismatchedDimensionException(
205: constructMessage("ptDst", dimPoint, dimTarget));
206: }
207: /*
208: * Transforms the coordinates using a temporary 'double[]' buffer,
209: * and copy the transformation result in the destination position.
210: */
211: final double[] array;
212: if (dimSource >= dimTarget) {
213: array = ptSrc.getCoordinates();
214: } else {
215: array = new double[dimTarget];
216: for (int i = dimSource; --i >= 0;) {
217: array[i] = ptSrc.getOrdinate(i);
218: }
219: }
220: transform(array, 0, array, 0, 1);
221: for (int i = dimTarget; --i >= 0;) {
222: ptDst.setOrdinate(i, array[i]);
223: }
224: } else {
225: /*
226: * Destination not set. We are going to create the destination here. Since we know
227: * that the destination will be the Geotools implementation, write directly into the
228: * 'ordinates' array.
229: */
230: final GeneralDirectPosition destination;
231: ptDst = destination = new GeneralDirectPosition(dimTarget);
232: final double[] source;
233: if (dimSource <= dimTarget) {
234: source = destination.ordinates;
235: for (int i = dimSource; --i >= 0;) {
236: source[i] = ptSrc.getOrdinate(i);
237: }
238: } else {
239: source = ptSrc.getCoordinates();
240: }
241: transform(source, 0, destination.ordinates, 0, 1);
242: }
243: return ptDst;
244: }
245:
246: /**
247: * Transforms a list of coordinate point ordinal values. The default implementation
248: * invokes {@link #transform(double[],int,double[],int,int)} using a temporary array
249: * of doubles.
250: */
251: public void transform(final float[] srcPts, final int srcOff,
252: final float[] dstPts, final int dstOff, final int numPts)
253: throws TransformException {
254: final int dimSource = getSourceDimensions();
255: final int dimTarget = getTargetDimensions();
256: final double[] tmpPts = new double[numPts
257: * Math.max(dimSource, dimTarget)];
258: for (int i = numPts * dimSource; --i >= 0;) {
259: tmpPts[i] = srcPts[srcOff + i];
260: }
261: transform(tmpPts, 0, tmpPts, 0, numPts);
262: for (int i = numPts * dimTarget; --i >= 0;) {
263: dstPts[dstOff + i] = (float) tmpPts[i];
264: }
265: }
266:
267: /**
268: * Transform the specified shape. The default implementation computes
269: * quadratic curves using three points for each shape segments.
270: *
271: * @param shape Shape to transform.
272: * @return Transformed shape, or {@code shape} if this transform is the identity transform.
273: * @throws IllegalStateException if this transform doesn't map 2D coordinate systems.
274: * @throws TransformException if a transform failed.
275: *
276: * @see MathTransform2D#createTransformedShape(Shape)
277: */
278: public Shape createTransformedShape(final Shape shape)
279: throws TransformException {
280: return isIdentity() ? shape : createTransformedShape(shape,
281: null, null, ShapeUtilities.PARALLEL);
282: }
283:
284: /**
285: * Transforms a geometric shape. This method always copy transformed coordinates in a new
286: * object. The new object is usually a {@link GeneralPath}, but may also be a {@link Line2D}
287: * or a {@link QuadCurve2D} if such simplification is possible.
288: *
289: * @param shape The geometric shape to transform.
290: * @param preTransform An optional affine transform to apply <em>before</em> the
291: * transformation using {@code this}, or {@code null} if none.
292: * @param postTransform An optional affine transform to apply <em>after</em> the transformation
293: * using {@code this}, or {@code null} if none.
294: * @param orientation Base line of quadratic curves. Must be
295: * {@link ShapeUtilities#HORIZONTAL} or {@link ShapeUtilities#PARALLEL}).
296: *
297: * @return The transformed geometric shape.
298: * @throws MismatchedDimensionException if this transform doesn't is not two-dimensional.
299: * @throws TransformException If a transformation failed.
300: */
301: final Shape createTransformedShape(final Shape shape,
302: final AffineTransform preTransform,
303: final AffineTransform postTransform, final int orientation)
304: throws TransformException {
305: int dim;
306: if ((dim = getSourceDimensions()) != 2
307: || (dim = getTargetDimensions()) != 2) {
308: throw new MismatchedDimensionException(constructMessage(
309: "shape", 2, dim));
310: }
311: final PathIterator it = shape.getPathIterator(preTransform);
312: final GeneralPath path = new GeneralPath(it.getWindingRule());
313: final Point2D.Float ctrl = new Point2D.Float();
314: final double[] buffer = new double[6];
315:
316: double ax = 0, ay = 0; // Coordinate of the last point before transform.
317: double px = 0, py = 0; // Coordinate of the last point after transform.
318: for (; !it.isDone(); it.next()) {
319: switch (it.currentSegment(buffer)) {
320: default: {
321: throw new IllegalPathStateException();
322: }
323: case PathIterator.SEG_CLOSE: {
324: /*
325: * Closes the geometric shape and continues the loop. We use the 'continue'
326: * instruction here instead of 'break' because we don't want to execute the
327: * code after the switch (addition of transformed points into the path - there
328: * is no such point in a SEG_CLOSE).
329: */
330: path.closePath();
331: continue;
332: }
333: case PathIterator.SEG_MOVETO: {
334: /*
335: * Transforms the single point and adds it to the path. We use the 'continue'
336: * instruction here instead of 'break' because we don't want to execute the
337: * code after the switch (addition of a line or a curve - there is no such
338: * curve to add here; we are just moving the cursor).
339: */
340: ax = buffer[0];
341: ay = buffer[1];
342: transform(buffer, 0, buffer, 0, 1);
343: px = buffer[0];
344: py = buffer[1];
345: path.moveTo((float) px, (float) py);
346: continue;
347: }
348: case PathIterator.SEG_LINETO: {
349: /*
350: * Inserts a new control point at 'buffer[0,1]'. This control point will
351: * be initialised with coordinates in the middle of the straight line:
352: *
353: * x = 0.5*(x1+x2)
354: * y = 0.5*(y1+y2)
355: *
356: * This point will be transformed after the 'switch', which is why we use
357: * the 'break' statement here instead of 'continue' as in previous case.
358: */
359: buffer[0] = 0.5 * (ax + (ax = buffer[0]));
360: buffer[1] = 0.5 * (ay + (ay = buffer[1]));
361: buffer[2] = ax;
362: buffer[3] = ay;
363: break;
364: }
365: case PathIterator.SEG_QUADTO: {
366: /*
367: * Replaces the control point in 'buffer[0,1]' by a new control point lying
368: * on the quadratic curve. Coordinates for a point in the middle of the curve
369: * can be computed with:
370: *
371: * x = 0.5*(ctrlx + 0.5*(x1+x2))
372: * y = 0.5*(ctrly + 0.5*(y1+y2))
373: *
374: * There is no need to keep the old control point because it was not lying
375: * on the curve.
376: */
377: buffer[0] = 0.5 * (buffer[0] + 0.5 * (ax + (ax = buffer[2])));
378: buffer[1] = 0.5 * (buffer[1] + 0.5 * (ay + (ay = buffer[3])));
379: break;
380: }
381: case PathIterator.SEG_CUBICTO: {
382: /*
383: * Replaces the control point in 'buffer[0,1]' by a new control point lying
384: * on the cubic curve. Coordinates for a point in the middle of the curve
385: * can be computed with:
386: *
387: * x = 0.25*(1.5*(ctrlx1+ctrlx2) + 0.5*(x1+x2));
388: * y = 0.25*(1.5*(ctrly1+ctrly2) + 0.5*(y1+y2));
389: *
390: * There is no need to keep the old control point because it was not lying
391: * on the curve.
392: *
393: * NOTE: Le point calculé est bien sur la courbe, mais n'est pas
394: * nécessairement représentatif. Cet algorithme remplace les
395: * deux points de contrôles par un seul, ce qui se traduit par
396: * une perte de souplesse qui peut donner de mauvais résultats
397: * si la courbe cubique était bien tordue. Projeter une courbe
398: * cubique ne me semble pas être un problème simple, mais ce
399: * cas devrait être assez rare. Il se produira le plus souvent
400: * si on essaye de projeter un cercle ou une ellipse, auxquels
401: * cas l'algorithme actuel donnera quand même des résultats
402: * tolérables.
403: */
404: buffer[0] = 0.25 * (1.5 * (buffer[0] + buffer[2]) + 0.5 * (ax + (ax = buffer[4])));
405: buffer[1] = 0.25 * (1.5 * (buffer[1] + buffer[3]) + 0.5 * (ay + (ay = buffer[5])));
406: buffer[2] = ax;
407: buffer[3] = ay;
408: break;
409: }
410: }
411: /*
412: * Applies the transform on the point in the buffer, and append the transformed points
413: * to the general path. Try to add them as a quadratic line, or as a straight line if
414: * the computed control point is colinear with the starting and ending points.
415: */
416: transform(buffer, 0, buffer, 0, 2);
417: final Point2D ctrlPoint = ShapeUtilities
418: .parabolicControlPoint(px, py, buffer[0],
419: buffer[1], buffer[2], buffer[3],
420: orientation, ctrl);
421: px = buffer[2];
422: py = buffer[3];
423: if (ctrlPoint != null) {
424: assert ctrl == ctrlPoint;
425: path.quadTo(ctrl.x, ctrl.y, (float) px, (float) py);
426: } else {
427: path.lineTo((float) px, (float) py);
428: }
429: }
430: /*
431: * La projection de la forme géométrique est terminée. Applique
432: * une transformation affine si c'était demandée, puis retourne
433: * une version si possible simplifiée de la forme géométrique.
434: */
435: if (postTransform != null) {
436: path.transform(postTransform);
437: }
438: return ShapeUtilities.toPrimitive(path);
439: }
440:
441: /**
442: * Gets the derivative of this transform at a point. The default implementation always
443: * throw an exception. Subclasses that implement the {@link MathTransform2D} interface
444: * should override this method. Other subclasses should override
445: * {@link #derivative(DirectPosition)} instead.
446: *
447: * @param point The coordinate point where to evaluate the derivative.
448: * @return The derivative at the specified point as a 2×2 matrix.
449: * @throws MismatchedDimensionException if the input dimension is not 2.
450: * @throws TransformException if the derivative can't be evaluated at the specified point.
451: *
452: * @see MathTransform2D#derivative(Point2D)
453: */
454: public Matrix derivative(final Point2D point)
455: throws TransformException {
456: final int dimSource = getSourceDimensions();
457: if (dimSource != 2) {
458: throw new MismatchedDimensionException(constructMessage(
459: "point", 2, dimSource));
460: }
461: throw new TransformException(Errors
462: .format(ErrorKeys.CANT_COMPUTE_DERIVATIVE));
463: }
464:
465: /**
466: * Gets the derivative of this transform at a point. The default implementation
467: * ensure that {@code point} has a valid dimension. Next, it try to delegate
468: * the work to an other method:
469: *
470: * <ul>
471: * <li>If the input dimension is 2, then this method delegates the work to
472: * {@link #derivative(Point2D)}.</li>
473: * <li>If this object is an instance of {@link MathTransform1D}, then this
474: * method delegates the work to {@link MathTransform1D#derivative(double)
475: * derivative(double)}.</li>
476: * </ul>
477: *
478: * Otherwise, a {@link TransformException} is thrown.
479: *
480: * @param point The coordinate point where to evaluate the derivative.
481: * @return The derivative at the specified point (never {@code null}).
482: * @throws NullPointerException if the derivative dependents on coordinate
483: * and {@code point} is {@code null}.
484: * @throws MismatchedDimensionException if {@code point} doesn't have
485: * the expected dimension.
486: * @throws TransformException if the derivative can't be evaluated at the
487: * specified point.
488: */
489: public Matrix derivative(final DirectPosition point)
490: throws TransformException {
491: final int dimSource = getSourceDimensions();
492: if (point == null) {
493: if (dimSource == 2) {
494: return derivative((Point2D) null);
495: }
496: } else {
497: final int dimPoint = point.getDimension();
498: if (dimPoint != dimSource) {
499: throw new MismatchedDimensionException(
500: constructMessage("point", dimPoint, dimSource));
501: }
502: if (dimSource == 2) {
503: if (point instanceof Point2D) {
504: return derivative((Point2D) point);
505: }
506: return derivative(new Point2D.Double(point
507: .getOrdinate(0), point.getOrdinate(1)));
508: }
509: if (this instanceof MathTransform1D) {
510: return new Matrix1(((MathTransform1D) this )
511: .derivative(point.getOrdinate(0)));
512: }
513: }
514: throw new TransformException(Errors
515: .format(ErrorKeys.CANT_COMPUTE_DERIVATIVE));
516: }
517:
518: /**
519: * Creates the inverse transform of this object.
520: * The default implementation returns {@code this} if this transform is an identity
521: * transform, and throws a {@link NoninvertibleTransformException} otherwise. Subclasses
522: * should override this method.
523: */
524: public MathTransform inverse()
525: throws NoninvertibleTransformException {
526: if (isIdentity()) {
527: return this ;
528: }
529: throw new NoninvertibleTransformException(Errors
530: .format(ErrorKeys.NONINVERTIBLE_TRANSFORM));
531: }
532:
533: /**
534: * Concatenates in an optimized way a {@link MathTransform} {@code other} to this
535: * {@code MathTransform}. A new math transform is created to perform the combined
536: * transformation. The {@code applyOtherFirst} value determine the transformation
537: * order as bellow:
538: *
539: * <ul>
540: * <li>If {@code applyOtherFirst} is {@code true}, then transforming a point
541: * <var>p</var> by the combined transform is equivalent to first transforming
542: * <var>p</var> by {@code other} and then transforming the result by the
543: * original transform {@code this}.</li>
544: * <li>If {@code applyOtherFirst} is {@code false}, then transforming a point
545: * <var>p</var> by the combined transform is equivalent to first transforming
546: * <var>p</var> by the original transform {@code this} and then transforming
547: * the result by {@code other}.</li>
548: * </ul>
549: *
550: * If no special optimization is available for the combined transform, then this method
551: * returns {@code null}. In the later case, the concatenation will be prepared by
552: * {@link DefaultMathTransformFactory} using a generic {@link ConcatenatedTransform}.
553: *
554: * The default implementation always returns {@code null}. This method is ought to be
555: * overridden by subclasses capable of concatenating some combinaison of transforms in a
556: * special way. Examples are {@link ExponentialTransform1D} and {@link LogarithmicTransform1D}.
557: *
558: * @param other The math transform to apply.
559: * @param applyOtherFirst {@code true} if the transformation order is {@code other}
560: * followed by {@code this}, or {@code false} if the transformation order is
561: * {@code this} followed by {@code other}.
562: * @return The combined math transform, or {@code null} if no optimized combined
563: * transform is available.
564: */
565: MathTransform concatenate(final MathTransform other,
566: final boolean applyOtherFirst) {
567: return null;
568: }
569:
570: /**
571: * Returns a hash value for this transform.
572: */
573: public int hashCode() {
574: return getSourceDimensions() + 37 * getTargetDimensions();
575: }
576:
577: /**
578: * Compares the specified object with this math transform for equality.
579: * The default implementation checks if {@code object} is an instance
580: * of the same class than {@code this} and use the same parameter descriptor.
581: * Subclasses should override this method in order to compare internal fields.
582: */
583: public boolean equals(final Object object) {
584: // Do not check 'object==this' here, since this
585: // optimization is usually done in subclasses.
586: if (object != null && getClass().equals(object.getClass())) {
587: final AbstractMathTransform that = (AbstractMathTransform) object;
588: return Utilities.equals(this .getParameterDescriptors(),
589: that.getParameterDescriptors());
590: }
591: return false;
592: }
593:
594: /**
595: * Format the inner part of a
596: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
597: * Known Text</cite> (WKT)</A> element. The default implementation formats all parameter values
598: * returned by {@link #getParameterValues}. The parameter group name is used as the math
599: * transform name.
600: *
601: * @param formatter The formatter to use.
602: * @return The WKT element name, which is {@code "PARAM_MT"} in the default implementation.
603: */
604: protected String formatWKT(final Formatter formatter) {
605: final ParameterValueGroup parameters = getParameterValues();
606: if (parameters != null) {
607: formatter.append(formatter.getName(parameters
608: .getDescriptor()));
609: formatter.append(parameters);
610: }
611: return "PARAM_MT";
612: }
613:
614: /**
615: * Makes sure that an argument is non-null. This is a
616: * convenience method for subclass constructors.
617: *
618: * @param name Argument name.
619: * @param object User argument.
620: * @throws InvalidParameterValueException if {@code object} is null.
621: */
622: protected static void ensureNonNull(final String name,
623: final Object object) throws IllegalArgumentException {
624: if (object == null) {
625: throw new InvalidParameterValueException(Errors.format(
626: ErrorKeys.NULL_ARGUMENT_$1, name), name, object);
627: }
628: }
629:
630: /**
631: * Checks if source coordinates need to be copied before to apply the transformation.
632: * This convenience method is provided for {@code transform(...)} method implementation.
633: * This method make the following assumptions:
634: * <BR><BR>
635: * <UL>
636: * <LI>Coordinates will be iterated from lower index to upper index.</LI>
637: * <LI>Coordinates are read and writen in shrunk. For example (longitude,latitude,height)
638: * values for one coordinate are read together, and the transformed (x,y,z) values are
639: * written together only after.</LI>
640: * </UL>
641: * <BR><BR>
642: * However, this method does not assumes that source and target dimension are the same (in the
643: * special case where source and target dimension are always the same, a simplier and more
644: * efficient check is possible). The following example prepares a transformation from 2
645: * dimensional points to three dimensional points:
646: * <BR><BR>
647: * <blockquote><pre>
648: * public void transform(double[] srcPts, int srcOff,
649: * double[] dstPts, int dstOff, int numPts)
650: * {
651: * if (srcPts==dstPts && <strong>needCopy</strong>(srcOff, 2, dstOff, 3, numPts) {
652: * final double[] old = srcPts;
653: * srcPts = new double[numPts*2];
654: * System.arraycopy(old, srcOff, srcPts, 0, srcPts.length);
655: * srcOff = 0;
656: * }
657: * }</pre><blockquote>
658: */
659: protected static boolean needCopy(final int srcOff,
660: final int dimSource, final int dstOff, final int dimTarget,
661: final int numPts) {
662: if (numPts <= 1 || (srcOff >= dstOff && dimSource >= dimTarget)) {
663: /*
664: * Source coordinates are stored after target coordinates. If implementation
665: * read coordinates from lower index to upper index, then the destination will
666: * not overwrite the source coordinates, even if there is an overlaps.
667: */
668: return false;
669: }
670: return srcOff < dstOff + numPts * dimTarget
671: && dstOff < srcOff + numPts * dimSource;
672: }
673:
674: /**
675: * Ensures that the specified longitude stay within ±π radians. This method
676: * is typically invoked after geographic coordinates are transformed. This method may add
677: * or substract some amount of 2π radians to <var>x</var>.
678: *
679: * @param x The longitude in radians.
680: * @return The longitude in the range ±π radians.
681: */
682: protected static double rollLongitude(final double x) {
683: return x - (2 * Math.PI) * Math.floor(x / (2 * Math.PI) + 0.5);
684: }
685:
686: /**
687: * Wraps the specified matrix in a Geotools implementation of {@link Matrix}. If {@code matrix}
688: * is already an instance of {@code XMatrix}, then it is returned unchanged. Otherwise, all
689: * elements are copied in a new {@code XMatrix} object.
690: */
691: static XMatrix toXMatrix(final Matrix matrix) {
692: if (matrix instanceof XMatrix) {
693: return (XMatrix) matrix;
694: }
695: return MatrixFactory.create(matrix);
696: }
697:
698: /**
699: * Wraps the specified matrix in a Geotools implementation of {@link Matrix}. If {@code matrix}
700: * is already an instance of {@code GeneralMatrix}, then it is returned unchanged. Otherwise,
701: * all elements are copied in a new {@code GeneralMatrix} object.
702: * <p>
703: * Before to use this method, check if a {@link XMatrix} (to be obtained with {@link #toXMatrix})
704: * would be suffisient. Use this method only if a {@code GeneralMatrix} is really necessary.
705: */
706: static GeneralMatrix toGMatrix(final Matrix matrix) {
707: if (matrix instanceof GeneralMatrix) {
708: return (GeneralMatrix) matrix;
709: } else {
710: return new GeneralMatrix(matrix);
711: }
712: }
713:
714: /**
715: * Inverts the specified matrix in place. If the matrix can't be inverted (for example
716: * because of a {@link SingularMatrixException}), then the exception is wrapped into a
717: * {@link NoninvertibleTransformException}.
718: */
719: static Matrix invert(final Matrix matrix)
720: throws NoninvertibleTransformException {
721: try {
722: final XMatrix m = toXMatrix(matrix);
723: m.invert();
724: return m;
725: } catch (SingularMatrixException exception) {
726: NoninvertibleTransformException e = new NoninvertibleTransformException(
727: Errors.format(ErrorKeys.NONINVERTIBLE_TRANSFORM));
728: e.initCause(exception);
729: throw e;
730: }
731: }
732:
733: /**
734: * Default implementation for inverse math transform. This inner class is the inverse
735: * of the enclosing {@link MathTransform}. It is serializable only if the enclosing
736: * math transform is also serializable.
737: *
738: * @since 2.0
739: * @version $Id: AbstractMathTransform.java 24925 2007-03-27 20:12:08Z jgarnett $
740: * @author Martin Desruisseaux
741: */
742: protected abstract class Inverse extends AbstractMathTransform
743: implements Serializable {
744: /**
745: * Serial number for interoperability with different versions. This serial number is
746: * especilly important for inner classes, since the default {@code serialVersionUID}
747: * computation will not produce consistent results across implementations of different
748: * Java compiler. This is because different compilers may generate different names for
749: * synthetic members used in the implementation of inner classes. See:
750: *
751: * http://developer.java.sun.com/developer/bugParade/bugs/4211550.html
752: */
753: private static final long serialVersionUID = 3528274816628012283L;
754:
755: /**
756: * Constructs an inverse math transform.
757: */
758: protected Inverse() {
759: }
760:
761: /**
762: * Gets the dimension of input points. The default
763: * implementation returns the dimension of output
764: * points of the enclosing math transform.
765: */
766: public int getSourceDimensions() {
767: return AbstractMathTransform.this .getTargetDimensions();
768: }
769:
770: /**
771: * Gets the dimension of output points. The default
772: * implementation returns the dimension of input
773: * points of the enclosing math transform.
774: */
775: public int getTargetDimensions() {
776: return AbstractMathTransform.this .getSourceDimensions();
777: }
778:
779: /**
780: * Gets the derivative of this transform at a point. The default
781: * implementation compute the inverse of the matrix returned by
782: * the enclosing math transform.
783: */
784: public Matrix derivative(final Point2D point)
785: throws TransformException {
786: return invert(AbstractMathTransform.this .derivative(this
787: .transform(point, null)));
788: }
789:
790: /**
791: * Gets the derivative of this transform at a point. The default
792: * implementation compute the inverse of the matrix returned by
793: * the enclosing math transform.
794: */
795: public Matrix derivative(final DirectPosition point)
796: throws TransformException {
797: return invert(AbstractMathTransform.this .derivative(this
798: .transform(point, null)));
799: }
800:
801: /**
802: * Returns the inverse of this math transform, which is the enclosing math transform.
803: * This method is declared final because some implementation assume that the inverse
804: * of {@code this} is always {@code AbstractMathTransform.this}.
805: */
806: public final MathTransform inverse() {
807: return AbstractMathTransform.this ;
808: }
809:
810: /**
811: * Tests whether this transform does not move any points.
812: * The default implementation delegate this tests to the
813: * enclosing math transform.
814: */
815: public boolean isIdentity() {
816: return AbstractMathTransform.this .isIdentity();
817: }
818:
819: /**
820: * Returns a hash code value for this math transform.
821: */
822: public int hashCode() {
823: return ~AbstractMathTransform.this .hashCode();
824: }
825:
826: /**
827: * Compares the specified object with this inverse math transform for equality.
828: * The default implementation tests if {@code object} in an instance of the same
829: * class than {@code this}, and then test their enclosing math transforms.
830: */
831: public boolean equals(final Object object) {
832: if (object == this ) {
833: // Slight optimization
834: return true;
835: }
836: if (object instanceof Inverse) {
837: final Inverse that = (Inverse) object;
838: return Utilities.equals(this .inverse(), that.inverse());
839: } else {
840: return false;
841: }
842: }
843:
844: /**
845: * Format the inner part of a
846: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
847: * Known Text</cite> (WKT)</A> element. If this inverse math transform
848: * has any parameter values, then this method format the WKT as in the
849: * {@linkplain AbstractMathTransform#formatWKT super-class method}. Otherwise
850: * this method format the math transform as an <code>"INVERSE_MT"</code> entity.
851: *
852: * @param formatter The formatter to use.
853: * @return The WKT element name, which is <code>"PARAM_MT"</code> or
854: * <code>"INVERSE_MT"</code> in the default implementation.
855: */
856: protected String formatWKT(final Formatter formatter) {
857: final ParameterValueGroup parameters = getParameterValues();
858: if (parameters != null) {
859: formatter.append(formatter.getName(parameters
860: .getDescriptor()));
861: formatter.append(parameters);
862: return "PARAM_MT";
863: } else {
864: formatter
865: .append((Formattable) AbstractMathTransform.this );
866: return "INVERSE_MT";
867: }
868: }
869: }
870: }
|