001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/ct/MatrixTransform.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    // Geometry
054    import java.awt.geom.Point2D;
055    import java.io.Serializable;
056    import java.util.Arrays;
057    
058    import javax.media.jai.ParameterList;
059    import javax.media.jai.PerspectiveTransform;
060    import javax.media.jai.util.Range;
061    import javax.vecmath.GMatrix;
062    import javax.vecmath.SingularMatrixException;
063    
064    import org.deegree.model.csct.pt.CoordinatePoint;
065    import org.deegree.model.csct.pt.Matrix;
066    import org.deegree.model.csct.resources.css.ResourceKeys;
067    import org.deegree.model.csct.resources.css.Resources;
068    
069    /**
070     * Transforms multi-dimensional coordinate points using a {@link Matrix}.
071     * 
072     * @version 1.00
073     * @author OpenGIS (www.opengis.org)
074     * @author Martin Desruisseaux
075     */
076    final class MatrixTransform extends AbstractMathTransform implements Serializable {
077        /**
078         * Serial number for interoperability with different versions.
079         */
080        private static final long serialVersionUID = -2104496465933824935L;
081    
082        /**
083         * the number of rows.
084         */
085        private final int numRow;
086    
087        /**
088         * the number of columns.
089         */
090        private final int numCol;
091    
092        /**
093         * Elements of the matrix. Column indice vary fastest.
094         */
095        private final double[] elt;
096    
097        /**
098         * Construct a transform.
099         */
100        protected MatrixTransform( final GMatrix matrix ) {
101            numRow = matrix.getNumRow();
102            numCol = matrix.getNumCol();
103            elt = new double[numRow * numCol];
104            int index = 0;
105            for ( int j = 0; j < numRow; j++ )
106                for ( int i = 0; i < numCol; i++ )
107                    elt[index++] = matrix.getElement( j, i );
108        }
109    
110        /**
111         * Transforms an array of floating point coordinates by this matrix. Point coordinates must have
112         * a dimension equals to <code>{@link Matrix#getNumCol()}-1</code>. For example, for square
113         * matrix of size 4&times;4, coordinate points are three-dimensional and stored in the arrays
114         * starting at the specified offset (<code>srcOff</code>) in the order
115         * <code>[x<sub>0</sub>, y<sub>0</sub>, z<sub>0</sub>,
116         *        x<sub>1</sub>, y<sub>1</sub>, z<sub>1</sub>...,
117         *        x<sub>n</sub>, y<sub>n</sub>, z<sub>n</sub>]</code>.
118         * 
119         * The transformed points <code>(x',y',z')</code> are computed as below (note that this
120         * computation is similar to {@link PerspectiveTransform}):
121         * 
122         * <blockquote>
123         * 
124         * <pre>
125         *  [ u ]     [ m&lt;sub&gt;00&lt;/sub&gt;  m&lt;sub&gt;01&lt;/sub&gt;  m&lt;sub&gt;02&lt;/sub&gt;  m&lt;sub&gt;03&lt;/sub&gt; ] [ x ]
126         *  [ v ]  =  [ m&lt;sub&gt;10&lt;/sub&gt;  m&lt;sub&gt;11&lt;/sub&gt;  m&lt;sub&gt;12&lt;/sub&gt;  m&lt;sub&gt;13&lt;/sub&gt; ] [ y ]
127         *  [ w ]     [ m&lt;sub&gt;20&lt;/sub&gt;  m&lt;sub&gt;21&lt;/sub&gt;  m&lt;sub&gt;22&lt;/sub&gt;  m&lt;sub&gt;23&lt;/sub&gt; ] [ z ]
128         *  [ t ]     [ m&lt;sub&gt;30&lt;/sub&gt;  m&lt;sub&gt;31&lt;/sub&gt;  m&lt;sub&gt;32&lt;/sub&gt;  m&lt;sub&gt;33&lt;/sub&gt; ] [ 1 ]
129         * 
130         *    x' = u/t
131         *    y' = v/t
132         *    y' = w/t
133         * </pre>
134         * 
135         * </blockquote>
136         * 
137         * @param srcPts
138         *            The array containing the source point coordinates.
139         * @param srcOff
140         *            The offset to the first point to be transformed in the source array.
141         * @param dstPts
142         *            The array into which the transformed point coordinates are returned.
143         * @param dstOff
144         *            The offset to the location of the first transformed point that is stored in the
145         *            destination array. The source and destination array sections can be overlaps.
146         * @param numPts
147         *            The number of points to be transformed
148         */
149        public void transform( float[] srcPts, int srcOff, final float[] dstPts, int dstOff, int numPts ) {
150            final int inputDimension = numCol - 1; // The last ordinate will be assumed equals to 1.
151            final int outputDimension = numRow - 1;
152            final double[] buffer = new double[numRow];
153            if ( srcPts == dstPts ) {
154                // We are going to write in the source array. Checks if
155                // source and destination sections are going to clash.
156                final int upperSrc = srcOff + numPts * inputDimension;
157                if ( upperSrc > dstOff ) {
158                    if ( inputDimension >= outputDimension ? dstOff > srcOff
159                                                          : dstOff + numPts * outputDimension > upperSrc ) {
160                        // If source overlaps destination, then the easiest workaround is
161                        // to copy source data. This is not the most efficient however...
162                        srcPts = new float[numPts * inputDimension];
163                        System.arraycopy( dstPts, srcOff, srcPts, 0, srcPts.length );
164                        srcOff = 0;
165                    }
166                }
167            }
168            while ( --numPts >= 0 ) {
169                int mix = 0;
170                for ( int j = 0; j < numRow; j++ ) {
171                    double sum = elt[mix + inputDimension];
172                    for ( int i = 0; i < inputDimension; i++ ) {
173                        sum += srcPts[srcOff + i] * elt[mix++];
174                    }
175                    buffer[j] = sum;
176                    mix++;
177                }
178                final double w = buffer[outputDimension];
179                for ( int j = 0; j < outputDimension; j++ ) {
180                    // 'w' is equals to 1 if the transform is affine.
181                    dstPts[dstOff++] = (float) ( buffer[j] / w );
182                }
183                srcOff += inputDimension;
184            }
185        }
186    
187        /**
188         * Transforms an array of floating point coordinates by this matrix. Point coordinates must have
189         * a dimension equals to <code>{@link Matrix#getNumCol()}-1</code>. For example, for square
190         * matrix of size 4&times;4, coordinate points are three-dimensional and stored in the arrays
191         * starting at the specified offset (<code>srcOff</code>) in the order
192         * <code>[x<sub>0</sub>, y<sub>0</sub>, z<sub>0</sub>,
193         *        x<sub>1</sub>, y<sub>1</sub>, z<sub>1</sub>...,
194         *        x<sub>n</sub>, y<sub>n</sub>, z<sub>n</sub>]</code>.
195         * 
196         * The transformed points <code>(x',y',z')</code> are computed as below (note that this
197         * computation is similar to {@link PerspectiveTransform}):
198         * 
199         * <blockquote>
200         * 
201         * <pre>
202         *  [ u ]     [ m&lt;sub&gt;00&lt;/sub&gt;  m&lt;sub&gt;01&lt;/sub&gt;  m&lt;sub&gt;02&lt;/sub&gt;  m&lt;sub&gt;03&lt;/sub&gt; ] [ x ]
203         *  [ v ]  =  [ m&lt;sub&gt;10&lt;/sub&gt;  m&lt;sub&gt;11&lt;/sub&gt;  m&lt;sub&gt;12&lt;/sub&gt;  m&lt;sub&gt;13&lt;/sub&gt; ] [ y ]
204         *  [ w ]     [ m&lt;sub&gt;20&lt;/sub&gt;  m&lt;sub&gt;21&lt;/sub&gt;  m&lt;sub&gt;22&lt;/sub&gt;  m&lt;sub&gt;23&lt;/sub&gt; ] [ z ]
205         *  [ t ]     [ m&lt;sub&gt;30&lt;/sub&gt;  m&lt;sub&gt;31&lt;/sub&gt;  m&lt;sub&gt;32&lt;/sub&gt;  m&lt;sub&gt;33&lt;/sub&gt; ] [ 1 ]
206         * 
207         *    x' = u/t
208         *    y' = v/t
209         *    y' = w/t
210         * </pre>
211         * 
212         * </blockquote>
213         * 
214         * @param srcPts
215         *            The array containing the source point coordinates.
216         * @param srcOff
217         *            The offset to the first point to be transformed in the source array.
218         * @param dstPts
219         *            The array into which the transformed point coordinates are returned.
220         * @param dstOff
221         *            The offset to the location of the first transformed point that is stored in the
222         *            destination array. The source and destination array sections can be overlaps.
223         * @param numPts
224         *            The number of points to be transformed
225         */
226        public void transform( double[] srcPts, int srcOff, final double[] dstPts, int dstOff,
227                               int numPts ) {
228            final int inputDimension = numCol - 1; // The last ordinate will be assumed equals to 1.
229            final int outputDimension = numRow - 1;
230            final double[] buffer = new double[numRow];
231            if ( srcPts == dstPts ) {
232                // We are going to write in the source array. Checks if
233                // source and destination sections are going to clash.
234                final int upperSrc = srcOff + numPts * inputDimension;
235                if ( upperSrc > dstOff ) {
236                    if ( inputDimension >= outputDimension ? dstOff > srcOff
237                                                          : dstOff + numPts * outputDimension > upperSrc ) {
238                        // If source overlaps destination, then the easiest workaround is
239                        // to copy source data. This is not the most efficient however...
240                        srcPts = new double[numPts * inputDimension];
241                        System.arraycopy( dstPts, srcOff, srcPts, 0, srcPts.length );
242                        srcOff = 0;
243                    }
244                }
245            }
246            while ( --numPts >= 0 ) {
247                int mix = 0;
248                for ( int j = 0; j < numRow; j++ ) {
249                    double sum = elt[mix + inputDimension];
250                    for ( int i = 0; i < inputDimension; i++ ) {
251                        sum += srcPts[srcOff + i] * elt[mix++];
252                    }
253                    buffer[j] = sum;
254                    mix++;
255                }
256                final double w = buffer[outputDimension];
257                for ( int j = 0; j < outputDimension; j++ ) {
258                    // 'w' is equals to 1 if the transform is affine.
259                    dstPts[dstOff++] = buffer[j] / w;
260                }
261                srcOff += inputDimension;
262            }
263        }
264    
265        /**
266         * Gets the derivative of this transform at a point. For a matrix transform, the derivative is
267         * the same everywhere.
268         */
269        public Matrix derivative( final Point2D point ) {
270            return derivative( (CoordinatePoint) null );
271        }
272    
273        /**
274         * Gets the derivative of this transform at a point. For a matrix transform, the derivative is
275         * the same everywhere.
276         */
277        public Matrix derivative( final CoordinatePoint point ) {
278            final Matrix matrix = getMatrix();
279            matrix.setSize( numRow - 1, numCol - 1 );
280            return matrix;
281        }
282    
283        /**
284         * Returns a copy of the matrix.
285         */
286        public Matrix getMatrix() {
287            return new Matrix( numRow, numCol, elt );
288        }
289    
290        /**
291         * Gets the dimension of input points.
292         */
293        public int getDimSource() {
294            return numCol - 1;
295        }
296    
297        /**
298         * Gets the dimension of output points.
299         */
300        public int getDimTarget() {
301            return numRow - 1;
302        }
303    
304        /**
305         * Tests whether this transform does not move any points.
306         */
307        public boolean isIdentity() {
308            if ( numRow != numCol )
309                return false;
310    
311            int index = 0;
312            for ( int j = 0; j < numRow; j++ )
313                for ( int i = 0; i < numCol; i++ )
314                    if ( elt[index++] != ( i == j ? 1 : 0 ) )
315                        return false;
316            return true;
317        }
318    
319        /**
320         * Creates the inverse transform of this object.
321         */
322        public MathTransform inverse()
323                                throws NoninvertibleTransformException {
324            if ( isIdentity() )
325                return this;
326            final Matrix matrix = getMatrix();
327            try {
328                matrix.invert();
329            } catch ( SingularMatrixException exception ) {
330                NoninvertibleTransformException e = new NoninvertibleTransformException(
331                                                                                         Resources.format( ResourceKeys.ERROR_NONINVERTIBLE_TRANSFORM ) );
332                throw e;
333            }
334            return new MatrixTransform( matrix );
335        }
336    
337        /**
338         * Returns a hash value for this transform. This value need not remain consistent between
339         * different implementations of the same class.
340         */
341        public int hashCode() {
342            long code = 2563217;
343            for ( int i = elt.length; --i >= 0; ) {
344                code = code * 37 + Double.doubleToLongBits( elt[i] );
345            }
346            return (int) ( code >>> 32 ) ^ (int) code;
347        }
348    
349        /**
350         * Compares the specified object with this math transform for equality.
351         */
352        public boolean equals( final Object object ) {
353            if ( object == this )
354                return true; // Slight optimization
355            if ( super.equals( object ) ) {
356                final MatrixTransform that = (MatrixTransform) object;
357                return this.numRow == that.numRow && this.numCol == that.numCol
358                       && Arrays.equals( this.elt, that.elt );
359            }
360            return false;
361        }
362    
363        /**
364         * Returns the WKT for this math transform.
365         */
366        public String toString() {
367            return toString( getMatrix() );
368        }
369    
370        /**
371         * Returns the WKT for an affine transform using the specified matrix.
372         */
373        static String toString( final Matrix matrix ) {
374            final int numRow = matrix.getNumRow();
375            final int numCol = matrix.getNumCol();
376            final StringBuffer buffer = paramMT( "Affine" );
377            final StringBuffer eltBuf = new StringBuffer( "elt_" );
378            addParameter( buffer, "Num_row", numRow );
379            addParameter( buffer, "Num_col", numCol );
380            for ( int j = 0; j < numRow; j++ ) {
381                for ( int i = 0; i < numCol; i++ ) {
382                    final double value = matrix.getElement( j, i );
383                    if ( value != ( i == j ? 1 : 0 ) ) {
384                        eltBuf.setLength( 4 );
385                        eltBuf.append( j );
386                        eltBuf.append( '_' );
387                        eltBuf.append( i );
388                        addParameter( buffer, eltBuf.toString(), value );
389                    }
390                }
391            }
392            buffer.append( ']' );
393            return buffer.toString();
394        }
395    
396        /**
397         * The provider for {@link MatrixTransform}.
398         * 
399         * @version 1.0
400         * @author Martin Desruisseaux
401         */
402        static final class Provider extends MathTransformProvider {
403            /**
404             * Range of positives values. Range goes from 1 to the maximum value.
405             */
406            private static final Range POSITIVE_RANGE = new Range( Integer.class, new Integer( 1 ),
407                                                                   new Integer( Integer.MAX_VALUE ) );
408    
409            /**
410             * Create a provider for affine transforms of the specified dimension. Created affine
411             * transforms will have a size of <code>numRow&nbsp;&times;&nbsp;numCol</code>.
412             * 
413             * @param numRow
414             *            The number of matrix's rows.
415             * @param numCol
416             *            The number of matrix's columns.
417             */
418            public Provider( final int numRow, final int numCol ) {
419                super( "Affine", ResourceKeys.AFFINE_TRANSFORM, null );
420                putInt( "Num_row", numRow, POSITIVE_RANGE ); // Add integer (not double) parameter
421                putInt( "Num_col", numCol, POSITIVE_RANGE ); // Add integer (not double) parameter
422                final StringBuffer buffer = new StringBuffer( "elt_" );
423                for ( int j = 0; j <= numRow; j++ ) {
424                    for ( int i = 0; i <= numCol; i++ ) {
425                        buffer.setLength( 4 );
426                        buffer.append( j );
427                        buffer.append( '_' );
428                        buffer.append( i );
429                        put( buffer.toString(), ( i == j ) ? 1.0 : 0.0, null );
430                    }
431                }
432            }
433    
434            /**
435             * Returns a transform for the specified parameters.
436             * 
437             * @param parameters
438             *            The parameter values in standard units.
439             * @return A {@link MathTransform} object of this classification.
440             */
441            public MathTransform create( final ParameterList parameters ) {
442                return staticCreate( parameters );
443            }
444    
445            /**
446             * Static version of {@link #create}, for use by
447             * {@link MathTransformFactory#createParameterizedTransform(String, ParameterList)}.
448             */
449            public static MathTransform staticCreate( final ParameterList parameters ) {
450                final int numRow = parameters.getIntParameter( "Num_row" );
451                final int numCol = parameters.getIntParameter( "Num_col" );
452                final Matrix matrix = new Matrix( numRow, numCol );
453                final String[] names = parameters.getParameterListDescriptor().getParamNames();
454                if ( names != null ) {
455                    for ( int i = 0; i < names.length; i++ ) {
456                        final String name = names[i];
457                        if ( name.regionMatches( true, 0, "elt_", 0, 4 ) ) {
458                            final int separator = name.lastIndexOf( '_' );
459                            final int row = Integer.parseInt( name.substring( 4, separator ) );
460                            final int col = Integer.parseInt( name.substring( separator + 1 ) );
461                            matrix.setElement( row, col, parameters.getDoubleParameter( name ) );
462                        }
463                    }
464                }
465                if ( numRow == 3 && matrix.isAffine() ) {
466                    return new AffineTransform2D( matrix.toAffineTransform2D() );
467                }
468                return new MatrixTransform( matrix );
469            }
470        }
471    }