001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/ct/AbstractMathTransform.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/exse/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification
012     (C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/)
013     SEAGIS Contacts:  Surveillance de l'Environnement Assist�e par Satellite
014     Institut de Recherche pour le D�veloppement / US-Espace
015     mailto:seasnet@teledetection.fr
016    
017    
018     This library is free software; you can redistribute it and/or
019     modify it under the terms of the GNU Lesser General Public
020     License as published by the Free Software Foundation; either
021     version 2.1 of the License, or (at your option) any later version.
022    
023     This library is distributed in the hope that it will be useful,
024     but WITHOUT ANY WARRANTY; without even the implied warranty of
025     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
026     Lesser General Public License for more details.
027    
028     You should have received a copy of the GNU Lesser General Public
029     License along with this library; if not, write to the Free Software
030     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
031    
032     Contact:
033    
034     Andreas Poth
035     lat/lon GmbH
036     Aennchenstr. 19
037     53115 Bonn
038     Germany
039     E-Mail: poth@lat-lon.de
040    
041     Klaus Greve
042     Department of Geography
043     University of Bonn
044     Meckenheimer Allee 166
045     53115 Bonn
046     Germany
047     E-Mail: klaus.greve@uni-bonn.de
048    
049     
050     ---------------------------------------------------------------------------*/
051    package org.deegree.model.csct.ct;
052    
053    // OpenGIS dependencies
054    import java.awt.Shape;
055    import java.awt.geom.AffineTransform;
056    import java.awt.geom.GeneralPath;
057    import java.awt.geom.IllegalPathStateException;
058    import java.awt.geom.Line2D;
059    import java.awt.geom.PathIterator;
060    import java.awt.geom.Point2D;
061    import java.awt.geom.QuadCurve2D;
062    
063    import javax.vecmath.SingularMatrixException;
064    
065    import org.deegree.model.csct.pt.CoordinatePoint;
066    import org.deegree.model.csct.pt.Matrix;
067    import org.deegree.model.csct.pt.MismatchedDimensionException;
068    import org.deegree.model.csct.resources.Geometry;
069    import org.deegree.model.csct.resources.Utilities;
070    import org.deegree.model.csct.resources.css.ResourceKeys;
071    import org.deegree.model.csct.resources.css.Resources;
072    
073    /**
074     * Provides a default implementations for most methods required by the
075     * {@link MathTransform} interface. <code>AbstractMathTransform</code>
076     * provides a convenient base class from which other transform classes
077     * can be easily derived. In addition, <code>AbstractMathTransform</code>
078     * implements methods required by the {@link MathTransform2D} interface,
079     * but <strong>does not</strong> implements <code>MathTransform2D</code>.
080     * Subclasses must declare <code>implements&nbsp;MathTransform2D</code>
081     * themself if they know to maps two-dimensional coordinate systems.
082     *
083     * @version 1.0
084     * @author Martin Desruisseaux
085     */
086    public abstract class AbstractMathTransform implements MathTransform {
087        /**
088         * Construct a math transform.
089         */
090        public AbstractMathTransform() {
091        }
092    
093        /**
094         * Returns a human readable name, if available. If no name is available in
095         * the specified locale,   then this method returns a name in an arbitrary
096         * locale. If no name is available in any locale, then this method returns
097         * <code>null</code>. The default implementation always returns <code>null</code>.
098         *
099         * @param  locale The desired locale, or <code>null</code> for a default locale.
100         * @return The transform name localized in the specified locale if possible, or
101         *         <code>null</code> if no name is available in any locale.
102         */
103        protected String getName( ) {
104            return null;
105        }
106    
107        /**
108         * Transforms the specified <code>ptSrc</code> and stores the result in <code>ptDst</code>.
109         * The default implementation invokes {@link #transform(double[],int,double[],int,int)}
110         * using a temporary array of doubles.
111         *
112         * @param ptSrc the specified coordinate point to be transformed.
113         * @param ptDst the specified coordinate point that stores the
114         *              result of transforming <code>ptSrc</code>, or
115         *              <code>null</code>.
116         * @return the coordinate point after transforming <code>ptSrc</code>
117         *         and stroring the result in <code>ptDst</code>.
118         * @throws MismatchedDimensionException if this transform
119         *         doesn't map two-dimensional coordinate systems.
120         * @throws TransformException if the point can't be transformed.
121         *
122         * @see MathTransform2D#transform(Point2D,Point2D)
123         */
124        public Point2D transform( final Point2D ptSrc, final Point2D ptDst )
125                                throws TransformException {
126            if ( getDimSource() != 2 || getDimTarget() != 2 ) {
127                throw new MismatchedDimensionException();
128            }
129            final double[] ord = new double[] { ptSrc.getX(), ptSrc.getY() };
130            this.transform( ord, 0, ord, 0, 1 );
131            if ( ptDst != null ) {
132                ptDst.setLocation( ord[0], ord[1] );
133                return ptDst;
134            }
135            return new Point2D.Double( ord[0], ord[1] );
136        }
137    
138        /**
139         * Transforms the specified <code>ptSrc</code> and stores the result
140         * in <code>ptDst</code>. The default implementation invokes
141         * {@link #transform(double[],int,double[],int,int)}.
142         */
143        public CoordinatePoint transform( final CoordinatePoint ptSrc, CoordinatePoint ptDst )
144                                throws TransformException {
145            final int pointDim = ptSrc.getDimension();
146            final int sourceDim = getDimSource();
147            final int targetDim = getDimTarget();
148            if ( pointDim != sourceDim ) {
149                throw new MismatchedDimensionException( pointDim, sourceDim );
150            }
151            if ( ptDst == null ) {
152                ptDst = new CoordinatePoint( targetDim );
153            } else if ( ptDst.getDimension() != targetDim ) {
154                throw new MismatchedDimensionException( ptDst.getDimension(), targetDim );
155            }
156            transform( ptSrc.ord, 0, ptDst.ord, 0, 1 );
157            return ptDst;
158        }
159    
160        /**
161         * Transforms a list of coordinate point ordinal values. The default implementation
162         * invokes {@link #transform(double[],int,double[],int,int)} using a temporary array
163         * of doubles.
164         */
165        public void transform( final float[] srcPts, final int srcOff, final float[] dstPts,
166                              final int dstOff, final int numPts )
167                                throws TransformException {
168            final int dimSource = getDimSource();
169            final int dimTarget = getDimTarget();
170            final double[] tmpPts = new double[numPts * Math.max( dimSource, dimTarget )];
171            for ( int i = numPts * dimSource; --i >= 0; )
172                tmpPts[i] = srcPts[srcOff + i];
173            transform( tmpPts, 0, tmpPts, 0, numPts );
174            for ( int i = numPts * dimTarget; --i >= 0; )
175                dstPts[dstOff + i] = (float) tmpPts[i];
176        }
177    
178        /**
179         * Transform the specified shape. The default implementation compute
180         * quadratic curves using three points for each shape's segments.
181         *
182         * @param  shape Shape to transform.
183         * @return Transformed shape, or <code>shape</code> if
184         *         this transform is the identity transform.
185         * @throws IllegalStateException if this transform doesn't map 2D coordinate systems.
186         * @throws TransformException if a transform failed.
187         *
188         * @see MathTransform2D#createTransformedShape(Shape)
189         */
190        public Shape createTransformedShape( final Shape shape )
191                                throws TransformException {
192            return isIdentity() ? shape : createTransformedShape( shape, null, null, Geometry.PARALLEL );
193        }
194    
195        /**
196         * Transforme une forme g�om�trique. Cette m�thode copie toujours les coordonn�es
197         * transform�es dans un nouvel objet. La plupart du temps, elle produira un objet
198         * {@link GeneralPath}. Elle peut aussi retourner des objets {@link Line2D} ou
199         * {@link QuadCurve2D} si une telle simplification est possible.
200         *
201         * @param  shape  Forme g�om�trique � transformer.
202         * @param  preTr  Transformation affine � appliquer <em>avant</em> de transformer la forme
203         *                <code>shape</code>, ou <code>null</code> pour ne pas en appliquer.
204         *                Cet argument sera surtout utile lors des transformations inverses.
205         * @param  postTr Transformation affine � appliquer <em>apr�s</em> avoir transform�e la
206         *                forme <code>shape</code>, ou <code>null</code> pour ne pas en appliquer.
207         *                Cet argument sera surtout utile lors des transformations directes.
208         * @param quadDir Direction des courbes quadratiques ({@link Geometry#HORIZONTAL}
209         *                ou {@link Geometry#PARALLEL}).
210         *
211         * @return La forme g�om�trique transform�e.
212         * @throws MismatchedDimensionException if this transform
213         *         doesn't map two-dimensional coordinate systems.
214         * @throws TransformException Si une transformation a �chou�.
215         */
216        final Shape createTransformedShape( final Shape shape, final AffineTransform preTr,
217                                           final AffineTransform postTr, final int quadDir )
218                                throws TransformException {
219            if ( getDimSource() != 2 || getDimTarget() != 2 ) {
220                throw new MismatchedDimensionException();
221            }
222            final PathIterator it = shape.getPathIterator( preTr );
223            final GeneralPath path = new GeneralPath( it.getWindingRule() );
224            final Point2D.Float ctrl = new Point2D.Float();
225            final double[] buffer = new double[6];
226    
227            double ax = 0, ay = 0; // Coordonn�es du dernier point avant la projection.
228            double px = 0, py = 0; // Coordonn�es du dernier point apr�s la projection.
229            int indexCtrlPt = 0; // Index du point de contr�le dans 'buffer'.
230            int indexLastPt = 0; // Index du dernier point dans 'buffer'.
231            for ( ; !it.isDone(); it.next() ) {
232                switch ( it.currentSegment( buffer ) ) {
233                default: {
234                    throw new IllegalPathStateException();
235                }
236                case PathIterator.SEG_CLOSE: {
237                    /*
238                     * Ferme la forme g�om�trique, puis continue la boucle. On utilise une
239                     * instruction 'continue' plut�t que 'break' car il ne faut pas ex�cuter
240                     * le code qui suit ce 'switch'.
241                     */
242                    path.closePath();
243                    continue;
244                }
245                case PathIterator.SEG_MOVETO: {
246                    /*
247                     * M�morise les coordonn�es sp�cifi�es (avant et apr�s les avoir
248                     * projet�es), puis continue la boucle. On utilise une instruction
249                     * 'continue' plut�t que 'break' car il ne faut pas ex�cuter le
250                     * code qui suit ce 'switch'.
251                     */
252                    ax = buffer[0];
253                    ay = buffer[1];
254                    transform( buffer, 0, buffer, 0, 1 );
255                    path.moveTo( (float) ( px = buffer[0] ), (float) ( py = buffer[1] ) );
256                    continue;
257                }
258                case PathIterator.SEG_LINETO: {
259                    /*
260                     * Place dans 'buffer[2,3]' les coordonn�es
261                     * d'un point qui se trouve sur la droite:
262                     *
263                     *  x = 0.5*(x1+x2)
264                     *  y = 0.5*(y1+y2)
265                     *
266                     * Ce point sera trait� apr�s le 'switch', d'o�
267                     * l'utilisation d'un 'break' plut�t que 'continue'.
268                     */
269                    indexLastPt = 0;
270                    indexCtrlPt = 2;
271                    buffer[2] = 0.5 * ( ax + ( ax = buffer[0] ) );
272                    buffer[3] = 0.5 * ( ay + ( ay = buffer[1] ) );
273                    break;
274                }
275                case PathIterator.SEG_QUADTO: {
276                    /*
277                     * Place dans 'buffer[0,1]' les coordonn�es
278                     * d'un point qui se trouve sur la courbe:
279                     *
280                     *  x = 0.5*(ctrlx + 0.5*(x1+x2))
281                     *  y = 0.5*(ctrly + 0.5*(y1+y2))
282                     *
283                     * Ce point sera trait� apr�s le 'switch', d'o�
284                     * l'utilisation d'un 'break' plut�t que 'continue'.
285                     */
286                    indexLastPt = 2;
287                    indexCtrlPt = 0;
288                    buffer[0] = 0.5 * ( buffer[0] + 0.5 * ( ax + ( ax = buffer[2] ) ) );
289                    buffer[1] = 0.5 * ( buffer[1] + 0.5 * ( ay + ( ay = buffer[3] ) ) );
290                    break;
291                }
292                case PathIterator.SEG_CUBICTO: {
293                    /*
294                     * Place dans 'buffer[0,1]' les coordonn�es
295                     * d'un point qui se trouve sur la courbe:
296                     *
297                     *  x = 0.25*(1.5*(ctrlx1+ctrlx2) + 0.5*(x1+x2));
298                     *  y = 0.25*(1.5*(ctrly1+ctrly2) + 0.5*(y1+y2));
299                     *
300                     * Ce point sera trait� apr�s le 'switch', d'o�
301                     * l'utilisation d'un 'break' plut�t que 'continue'.
302                     *
303                     * NOTE: Le point calcul� est bien sur la courbe, mais n'est pas n�cessairement repr�sentatif.
304                     *       Cet algorithme remplace les deux points de contr�les par un seul, ce qui se traduit
305                     *       par une perte de souplesse qui peut donner de mauvais r�sultats si la courbe cubique
306                     *       �tait bien tordue. Projeter une courbe cubique ne me semble pas �tre un probl�me simple,
307                     *       mais heureusement ce cas devrait �tre assez rare. Il se produira le plus souvent si on
308                     *       essaye de projeter un cercle ou une ellipse, auxquels cas l'algorithme actuel donnera
309                     *       quand m�me des r�sultats tol�rables.
310                     */
311                    indexLastPt = 4;
312                    indexCtrlPt = 0;
313                    buffer[0] = 0.25 * ( 1.5 * ( buffer[0] + buffer[2] ) + 0.5 * ( ax + ( ax = buffer[4] ) ) );
314                    buffer[1] = 0.25 * ( 1.5 * ( buffer[1] + buffer[3] ) + 0.5 * ( ay + ( ay = buffer[5] ) ) );
315                    break;
316                }
317                }
318                /*
319                 * Applique la transformation sur les points qui se
320                 * trouve dans le buffer, puis ajoute ces points �
321                 * la forme g�om�trique projet�e comme une courbe
322                 * quadratique.
323                 */
324                transform( buffer, 0, buffer, 0, 2 );
325                if ( Geometry.parabolicControlPoint( px, py, buffer[indexCtrlPt],
326                                                     buffer[indexCtrlPt + 1], buffer[indexLastPt],
327                                                     buffer[indexLastPt + 1], quadDir, ctrl ) != null ) {
328                    path.quadTo( ctrl.x, ctrl.y, (float) ( px = buffer[indexLastPt + 0] ),
329                                 (float) ( py = buffer[indexLastPt + 1] ) );
330                } else
331                    path.lineTo( (float) ( px = buffer[indexLastPt + 0] ),
332                                 (float) ( py = buffer[indexLastPt + 1] ) );
333            }
334            /*
335             * La projection de la forme g�om�trique est termin�e. Applique
336             * une transformation affine si c'�tait demand�e, puis retourne
337             * une version si possible simplifi�e de la forme g�om�trique.
338             */
339            if ( postTr != null ) {
340                path.transform( postTr );
341            }
342            return Geometry.toPrimitive( path );
343        }
344    
345        /**
346         * Gets the derivative of this transform at a point. The default
347         * implementation invokes {@link #derivative(CoordinatePoint)}.
348         *
349         * @param  point The coordinate point where to evaluate the derivative.
350         * @return The derivative at the specified point as a 2x2 matrix.
351         * @throws MismatchedDimensionException if the input dimension is not 2.
352         * @throws TransformException if the derivative can't be evaluated at the specified point.
353         *
354         * @see MathTransform2D#derivative(Point2D)
355         */
356        public Matrix derivative( final Point2D point )
357                                throws TransformException {
358            return derivative( new CoordinatePoint( point ) );
359        }
360    
361        /**
362         * Gets the derivative of this transform at a point. The default
363         * implementation throws an {@link UnsupportedOperationException}
364         * <strong>(note: this default implementation may change in a future
365         * version)</strong>.
366         *
367         * @param  point The coordinate point where to evaluate the derivative.
368         * @return The derivative at the specified point (never <code>null</code>).
369         * @throws TransformException if the derivative can't be evaluated at the specified point.
370         */
371        public Matrix derivative( final CoordinatePoint point )
372                                throws TransformException {
373            throw new UnsupportedOperationException( "Matrix derivative not yet implemented" );
374        }
375    
376        /**
377         * Creates the inverse transform of this object.
378         * The default implementation returns <code>this</code> if this transform is an identity
379         * transform, and throws a {@link NoninvertibleTransformException} otherwise. Subclasses
380         * should override this method.
381         */
382        public MathTransform inverse()
383                                throws NoninvertibleTransformException {
384            if ( isIdentity() )
385                return this;
386            throw new NoninvertibleTransformException(
387                                                       Resources.format( ResourceKeys.ERROR_NONINVERTIBLE_TRANSFORM ) );
388        }
389    
390        /**
391         * Returns a hash value for this transform.
392         */
393        public int hashCode() {
394            return getDimSource() + 37 * getDimTarget();
395        }
396    
397        /**
398         * Compares the specified object with this math transform for equality.
399         * The default implementation checks if <code>object</code> is an instance
400         * of the same class than <code>this</code>. Subclasses should override
401         * this method in order to compare internal fields.
402         */
403        public boolean equals( final Object object ) {
404            // Do not check 'object==this' here, since this
405            // optimization is usually done in subclasses.
406            return ( object != null && getClass().equals( object.getClass() ) );
407        }
408    
409        /**
410         * Returns a string repr�sentation of this transform.
411         * Subclasses should override this method in order to
412         * returns Well Know Text (WKT) instead.
413         */
414        public String toString() {
415            final StringBuffer buffer = new StringBuffer( Utilities.getShortClassName( this ) );
416            buffer.append( '[' );
417            buffer.append( getDimSource() );
418            buffer.append( "D \u2192 " ); // Arrow -->
419            buffer.append( getDimTarget() );
420            buffer.append( "D]" );
421            return buffer.toString();
422        }
423    
424        /**
425         * Returns a string buffer initialized with "PARAM_MT"
426         * and a classification name. This is a convenience
427         * method for WKT formatting.
428         */
429        static StringBuffer paramMT( final String classification ) {
430            final StringBuffer buffer = new StringBuffer( "PARAM_MT[\"" );
431            buffer.append( classification );
432            buffer.append( '"' );
433            return buffer;
434        }
435    
436        /**
437         * Add the <code>", PARAMETER["<name>", <value>]"</code> string
438         * to the specified string buffer. This is a convenience method
439         * for constructing WKT for "PARAM_MT".
440         */
441        static void addParameter( final StringBuffer buffer, final String key, final double value ) {
442            buffer.append( ", PARAMETER[\"" );
443            buffer.append( key );
444            buffer.append( "\"," );
445            buffer.append( value );
446            buffer.append( ']' );
447        }
448    
449        /**
450         * Add the <code>", PARAMETER["<name>", <value>]"</code> string
451         * to the specified string buffer. This is a convenience method
452         * for constructing WKT for "PARAM_MT".
453         */
454        static void addParameter( final StringBuffer buffer, final String key, final int value ) {
455            buffer.append( ", PARAMETER[\"" );
456            buffer.append( key );
457            buffer.append( "\"," );
458            buffer.append( value );
459            buffer.append( ']' );
460        }
461       
462        /**
463         * Invert the specified matrix in place. If the matrix can't be inverted
464         * because of a {@link SingularMatrixException}, then the exception is
465         * wrapped into a {@link NoninvertibleTransformException}.
466         */
467        private static Matrix invert( final Matrix matrix )
468                                throws NoninvertibleTransformException {
469            try {
470                matrix.invert();
471                return matrix;
472            } catch ( SingularMatrixException exception ) {
473                NoninvertibleTransformException e = new NoninvertibleTransformException(
474                                                                                         Resources.format( ResourceKeys.ERROR_NONINVERTIBLE_TRANSFORM ) );
475                throw e;
476            }
477        }
478    
479        /**
480         * Default implementation for inverse math transform.
481         * This inner class is the inverse of the enclosing
482         * math transform.
483         *
484         * @version 1.0
485         * @author Martin Desruisseaux
486         */
487        protected abstract class Inverse extends AbstractMathTransform {
488            /**
489             * Construct an inverse math transform.
490             */
491            public Inverse() {
492            }
493    
494            /**
495             * Gets the dimension of input points. The default
496             * implementation returns the dimension of output
497             * points of the enclosing math transform.
498             */
499            public int getDimSource() {
500                return AbstractMathTransform.this.getDimTarget();
501            }
502    
503            /**
504             * Gets the dimension of output points. The default
505             * implementation returns the dimension of input
506             * points of the enclosing math transform.
507             */
508            public int getDimTarget() {
509                return AbstractMathTransform.this.getDimSource();
510            }
511    
512            /**
513             * Gets the derivative of this transform at a point. The default
514             * implementation compute the inverse of the matrix returned by
515             * the enclosing math transform.
516             */
517            public Matrix derivative( final Point2D point )
518                                    throws TransformException {
519                return invert( AbstractMathTransform.this.derivative( point ) );
520            }
521    
522            /**
523             * Gets the derivative of this transform at a point. The default
524             * implementation compute the inverse of the matrix returned by
525             * the enclosing math transform.
526             */
527            public Matrix derivative( final CoordinatePoint point )
528                                    throws TransformException {
529                return invert( AbstractMathTransform.this.derivative( point ) );
530            }
531    
532            /**
533             * Returns the inverse of this math transform, which is the enclosing math transform.
534             * This method is declared final because some implementation assume that the inverse
535             * of <code>this</code> is always <code>AbstractMathTransform.this</code>.
536             */
537            public final MathTransform inverse() {
538                return AbstractMathTransform.this;
539            }
540    
541            /**
542             * Tests whether this transform does not move any points.
543             * The default implementation delegate this tests to the
544             * enclosing math transform.
545             */
546            public boolean isIdentity() {
547                return AbstractMathTransform.this.isIdentity();
548            }
549    
550            /**
551             * Returns a hash code value for this math transform.
552             */
553            public int hashCode() {
554                return ~AbstractMathTransform.this.hashCode();
555            }
556    
557            /**
558             * Compares the specified object with this inverse math
559             * transform for equality. The default implementation tests
560             * if <code>object</code> in an instance of the same class
561             * than <code>this</code>, and then test their enclosing
562             * math transforms.
563             */
564            public boolean equals( final Object object ) {
565                if ( object == this )
566                    return true; // Slight optimization
567                if ( object instanceof Inverse ) {
568                    final Inverse that = (Inverse) object;
569                    return Utilities.equals( this.inverse(), that.inverse() );
570                }
571                return false;
572            }
573    
574            /**
575             * Returns the Well Know Text (WKT)
576             * for this inverse math transform.
577             */
578            public String toString() {
579                return "INVERSE_MT[" + AbstractMathTransform.this + ']';
580            }
581        }
582    }