001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/pt/Matrix.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.pt;
052    
053    import java.awt.geom.AffineTransform;
054    import java.text.FieldPosition;
055    import java.text.NumberFormat;
056    
057    import javax.vecmath.GMatrix;
058    
059    import org.deegree.model.csct.cs.AxisOrientation;
060    import org.deegree.model.csct.resources.Utilities;
061    import org.deegree.model.csct.resources.css.ResourceKeys;
062    import org.deegree.model.csct.resources.css.Resources;
063    
064    /**
065     * A two dimensional array of numbers. Row and column numbering begins with zero.
066     * 
067     * @version 1.00
068     * @author OpenGIS (www.opengis.org)
069     * @author Martin Desruisseaux
070     * 
071     * @author last edited by: $Author: bezema $
072     * 
073     * @version $Revision: 6259 $, $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 2007) $
074     * 
075     * @see "org.opengis.pt.PT_Matrix"
076     * @see javax.vecmath.GMatrix
077     * @see java.awt.geom.AffineTransform
078     * @see javax.media.jai.PerspectiveTransform
079     * @see javax.media.j3d.Transform3D
080     * @see <A HREF="http://math.nist.gov/javanumerics/jama/">Jama matrix</A>
081     * @see <A HREF="http://jcp.org/jsr/detail/83.jsp">JSR-83 Multiarray package</A>
082     */
083    public class Matrix extends GMatrix {
084        /**
085         * Serial number for interoperability with different versions.
086         */
087        private static final long serialVersionUID = 3126899762163038129L;
088    
089        /**
090         * Construct a square identity matrix of size <code>size</code>&nbsp;&times;&nbsp;<code>size</code>.
091         * 
092         * @param size
093         */
094        public Matrix( final int size ) {
095            super( size, size );
096        }
097    
098        /**
099         * Construct a matrix of size <code>numRow</code>&nbsp;&times;&nbsp;<code>numCol</code>.
100         * Elements on the diagonal <var>j==i</var> are set to 1.
101         * 
102         * @param numRow
103         * @param numCol
104         */
105        public Matrix( final int numRow, final int numCol ) {
106            super( numRow, numCol );
107        }
108    
109        /**
110         * Constructs a <code>numRow</code>&nbsp;&times;&nbsp;<code>numCol</code> matrix
111         * initialized to the values in the <code>matrix</code> array. The array values are copied in
112         * one row at a time in row major fashion. The array should be exactly
113         * <code>numRow*numCol</code> in length. Note that because row and column numbering begins
114         * with zero, <code>row</code> and <code>numCol</code> will be one larger than the maximum
115         * possible matrix index values.
116         * 
117         * @param numRow
118         * @param numCol
119         * @param matrix
120         */
121        public Matrix( final int numRow, final int numCol, final double[] matrix ) {
122            super( numRow, numCol, matrix );
123            if ( numRow * numCol != matrix.length ) {
124                throw new IllegalArgumentException( String.valueOf( matrix.length ) );
125            }
126        }
127    
128        /**
129         * Constructs a new matrix from a two-dimensional array of doubles.
130         * 
131         * @param matrix
132         *            Array of rows. Each row must have the same length.
133         * @throws IllegalArgumentException
134         *             if the specified matrix is not regular (i.e. if all rows doesn't have the same
135         *             length).
136         */
137        public Matrix( final double[][] matrix ) throws IllegalArgumentException {
138            super( matrix.length, ( matrix.length != 0 ) ? matrix[0].length : 0 );
139            final int numRow = getNumRow();
140            final int numCol = getNumCol();
141            for ( int j = 0; j < numRow; j++ ) {
142                if ( matrix[j].length != numCol ) {
143                    throw new IllegalArgumentException(
144                                                        Resources.format( ResourceKeys.ERROR_MATRIX_NOT_REGULAR ) );
145                }
146                setRow( j, matrix[j] );
147            }
148        }
149    
150        /**
151         * Constructs a new matrix and copies the initial values from the parameter matrix.
152         * 
153         * @param matrix
154         */
155        public Matrix( final GMatrix matrix ) {
156            super( matrix );
157        }
158    
159        /**
160         * Construct a 3&times;3 matrix from the specified affine transform.
161         * 
162         * @param transform
163         */
164        public Matrix( final AffineTransform transform ) {
165            super( 3, 3, new double[] { transform.getScaleX(), transform.getShearX(),
166                                       transform.getTranslateX(), transform.getShearY(),
167                                       transform.getScaleY(), transform.getTranslateY(), 0, 0, 1 } );
168        }
169    
170        /**
171         * Construct an affine transform mapping a source region to a destination region. The regions
172         * must have the same number of dimensions, but their axis order and axis orientation may be
173         * different.
174         * 
175         * @param srcRegion
176         *            The source region.
177         * @param srcAxis
178         *            Axis orientation for each dimension of the source region.
179         * @param dstRegion
180         *            The destination region.
181         * @param dstAxis
182         *            Axis orientation for each dimension of the destination region.
183         * @param validRegions
184         *            <code>true</code> if source and destination regions must be taken in account. If
185         *            <code>false</code>, then source and destination regions will be ignored and may
186         *            be null.
187         */
188        private Matrix( final Envelope srcRegion, final AxisOrientation[] srcAxis,
189                        final Envelope dstRegion, final AxisOrientation[] dstAxis,
190                        final boolean validRegions ) {
191            this( srcAxis.length + 1 );
192            /*
193             * Arguments check. NOTE: those exceptions are catched by
194             * 'org.deegree.model.csct.ct.CoordinateTransformationFactory'. If exception type change,
195             * update the factory class.
196             */
197            final int dimension = srcAxis.length;
198            if ( dstAxis.length != dimension ) {
199                throw new MismatchedDimensionException( dimension, dstAxis.length );
200            }
201            if ( validRegions ) {
202                srcRegion.ensureDimensionMatch( dimension );
203                dstRegion.ensureDimensionMatch( dimension );
204            }
205            /*
206             * Map source axis to destination axis. If no axis is moved (for example if the user want to
207             * transform (NORTH,EAST) to (SOUTH,EAST)), then source and destination index will be equal.
208             * If some axis are moved (for example if the user want to transform (NORTH,EAST) to
209             * (EAST,NORTH)), then ordinates at index <code>srcIndex</code> will have to be moved at
210             * index <code>dstIndex</code>.
211             */
212            setZero();
213            for ( int srcIndex = 0; srcIndex < dimension; srcIndex++ ) {
214                boolean hasFound = false;
215                final AxisOrientation srcAxe = srcAxis[srcIndex];
216                final AxisOrientation search = srcAxe.absolute();
217                for ( int dstIndex = 0; dstIndex < dimension; dstIndex++ ) {
218                    final AxisOrientation dstAxe = dstAxis[dstIndex];
219                    if ( search.equals( dstAxe.absolute() ) ) {
220                        if ( hasFound ) {
221                            throw new IllegalArgumentException(
222                                                                Resources.format(
223                                                                                  ResourceKeys.ERROR_COLINEAR_AXIS_$2,
224                                                                                  srcAxe.getName( null ),
225                                                                                  dstAxe.getName( null ) ) );
226                        }
227                        hasFound = true;
228                        /*
229                         * Set the matrix elements. Some matrix elements will never be set. They will be
230                         * left to zero, which is their wanted value.
231                         */
232                        final boolean normal = srcAxe.equals( dstAxe );
233                        double scale = ( normal ) ? +1 : -1;
234                        double translate = 0;
235                        if ( validRegions ) {
236                            translate = ( normal ) ? dstRegion.getMinimum( dstIndex )
237                                                  : dstRegion.getMaximum( dstIndex );
238                            scale *= dstRegion.getLength( dstIndex ) / srcRegion.getLength( srcIndex );
239                            translate -= srcRegion.getMinimum( srcIndex ) * scale;
240                        }
241                        setElement( dstIndex, srcIndex, scale );
242                        setElement( dstIndex, dimension, translate );
243                    }
244                }
245                if ( !hasFound ) {
246                    throw new IllegalArgumentException(
247                                                        Resources.format(
248                                                                          ResourceKeys.ERROR_NO_DESTINATION_AXIS_$1,
249                                                                          srcAxis[srcIndex].getName( null ) ) );
250                }
251            }
252            setElement( dimension, dimension, 1 );
253    
254        }
255    
256        /**
257         * Construct an affine transform changing axis order and/or orientation. For example, the affine
258         * transform may convert (NORTH,WEST) coordinates into (EAST,NORTH). Axis orientation can be
259         * inversed only. For example, it is illegal to transform (NORTH,WEST) coordinates into
260         * (NORTH,DOWN).
261         * 
262         * @param srcAxis
263         *            The set of axis orientation for source coordinate system.
264         * @param dstAxis
265         *            The set of axis orientation for destination coordinate system.
266         * @return
267         * @throws MismatchedDimensionException
268         *             if <code>srcAxis</code> and <code>dstAxis</code> don't have the same length.
269         * @throws IllegalArgumentException
270         *             if the affine transform can't be created for some other raison.
271         */
272        public static Matrix createAffineTransform( final AxisOrientation[] srcAxis,
273                                                    final AxisOrientation[] dstAxis ) {
274            return new Matrix( null, srcAxis, null, dstAxis, false );
275        }
276    
277        /**
278         * Construct an affine transform that maps a source region to a destination region. Axis order
279         * and orientation are left unchanged.
280         * 
281         * @param srcRegion
282         *            The source region.
283         * @param dstRegion
284         *            The destination region.
285         * @throws MismatchedDimensionException
286         *             if regions don't have the same dimension.
287         */
288        public static Matrix createAffineTransform( final Envelope srcRegion, final Envelope dstRegion ) {
289            final int dimension = srcRegion.getDimension();
290            dstRegion.ensureDimensionMatch( dimension );
291            final Matrix matrix = new Matrix( dimension + 1 );
292            for ( int i = 0; i < dimension; i++ ) {
293                final double scale = dstRegion.getLength( i ) / srcRegion.getLength( i );
294                final double translate = dstRegion.getMinimum( i ) - srcRegion.getMinimum( i ) * scale;
295                matrix.setElement( i, i, scale );
296                matrix.setElement( i, dimension, translate );
297            }
298            matrix.setElement( dimension, dimension, 1 );
299            return matrix;
300        }
301    
302        /**
303         * Construct an affine transform mapping a source region to a destination region. Axis order
304         * and/or orientation can be changed during the process. For example, the affine transform may
305         * convert (NORTH,WEST) coordinates into (EAST,NORTH). Axis orientation can be inversed only.
306         * For example, it is illegal to transform (NORTH,WEST) coordinates into (NORTH,DOWN).
307         * 
308         * @param srcRegion
309         *            The source region.
310         * @param srcAxis
311         *            Axis orientation for each dimension of the source region.
312         * @param dstRegion
313         *            The destination region.
314         * @param dstAxis
315         *            Axis orientation for each dimension of the destination region.
316         * @return
317         * @throws MismatchedDimensionException
318         *             if all arguments don't have the same dimension.
319         * @throws IllegalArgumentException
320         *             if the affine transform can't be created for some other raison.
321         */
322        public static Matrix createAffineTransform( final Envelope srcRegion,
323                                                    final AxisOrientation[] srcAxis,
324                                                    final Envelope dstRegion,
325                                                    final AxisOrientation[] dstAxis ) {
326            return new Matrix( srcRegion, srcAxis, dstRegion, dstAxis, true );
327        }
328    
329        /**
330         * Retrieves the specifiable values in the transformation matrix into a 2-dimensional array of
331         * double precision values. The values are stored into the 2-dimensional array using the row
332         * index as the first subscript and the column index as the second. Values are copied; changes
333         * to the returned array will not change this matrix.
334         * 
335         * @return
336         * 
337         * @see "org.opengis.pt.PT_Matrix#elt"
338         */
339        public final double[][] getElements() {
340            final int numCol = getNumCol();
341            final double[][] matrix = new double[getNumRow()][];
342            for ( int j = 0; j < matrix.length; j++ ) {
343                getRow( j, matrix[j] = new double[numCol] );
344            }
345            return matrix;
346        }
347    
348        /**
349         * Returns <code>true</code> if this matrix is an affine transform. A transform is affine if
350         * the matrix is square and last row contains only zeros, except in the last column which
351         * contains 1.
352         * 
353         * @return <code>true</code> if this matrix is an affine transform.
354         */
355        public final boolean isAffine() {
356            int dimension = getNumRow();
357            if ( dimension != getNumCol() )
358                return false;
359    
360            dimension--;
361            for ( int i = 0; i <= dimension; i++ )
362                if ( getElement( dimension, i ) != ( i == dimension ? 1 : 0 ) )
363                    return false;
364            return true;
365        }
366    
367        /**
368         * Returns <code>true</code> if this matrix is an identity matrix.
369         * 
370         * @return <code>true</code> if this matrix is an identity matrix.
371         */
372        public final boolean isIdentity() {
373            final int numRow = getNumRow();
374            final int numCol = getNumCol();
375            if ( numRow != numCol )
376                return false;
377    
378            for ( int j = 0; j < numRow; j++ )
379                for ( int i = 0; i < numCol; i++ )
380                    if ( getElement( j, i ) != ( i == j ? 1 : 0 ) )
381                        return false;
382            return true;
383        }
384    
385        /**
386         * Returns an affine transform for this matrix. This is a convenience method for
387         * interoperability with Java2D.
388         * 
389         * @return an affine transform for this matrix.
390         * 
391         * @throws IllegalStateException
392         *             if this matrix is not 3x3, or if the last row is not [0 0 1].
393         */
394        public final AffineTransform toAffineTransform2D()
395                                throws IllegalStateException {
396            int check;
397            if ( ( check = getNumRow() ) != 3 || ( check = getNumCol() ) != 3 ) {
398                throw new IllegalStateException(
399                                                 Resources.format(
400                                                                   ResourceKeys.ERROR_NOT_TWO_DIMENSIONAL_$1,
401                                                                   new Integer( check - 1 ) ) );
402            }
403            if ( isAffine() ) {
404                return new AffineTransform( getElement( 0, 0 ), getElement( 1, 0 ), getElement( 0, 1 ),
405                                            getElement( 1, 1 ), getElement( 0, 2 ), getElement( 1, 2 ) );
406            }
407            throw new IllegalStateException(
408                                             Resources.format( ResourceKeys.ERROR_NOT_AN_AFFINE_TRANSFORM ) );
409        }
410    
411        /**
412         * Returns a string representation of this matrix. The returned string is implementation
413         * dependent. It is usually provided for debugging purposes only.
414         * 
415         * @return a string representation of this matrix. The returned string is implementation
416         *         dependent. It is usually provided for debugging purposes only.
417         */
418        public String toString() {
419            final int numRow = getNumRow();
420            final int numCol = getNumCol();
421            StringBuffer buffer = new StringBuffer( 10000 );
422            final int columnWidth = 12;
423            final String lineSeparator = System.getProperty( "line.separator", "\n" );
424            final FieldPosition dummy = new FieldPosition( 0 );
425            final NumberFormat format = NumberFormat.getNumberInstance();
426            format.setMinimumFractionDigits( 6 );
427            format.setMaximumFractionDigits( 6 );
428            for ( int j = 0; j < numRow; j++ ) {
429                for ( int i = 0; i < numCol; i++ ) {
430                    final int position = buffer.length();
431                    buffer = format.format( getElement( j, i ), buffer, dummy );
432                    buffer.insert( position, Utilities.spaces( columnWidth
433                                                               - ( buffer.length() - position ) ) );
434                }
435                buffer.append( lineSeparator );
436            }
437            return buffer.toString();
438        }
439    }