001    //$HeadURL: https://sushibar/svn/deegree/base/trunk/src/org/deegree/model/csct/pt/Matrix.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003     This file is part of deegree.
004     Copyright (C) 2001-2008 by:
005     Department of Geography, University of Bonn
006     http://www.giub.uni-bonn.de/deegree/
007     lat/lon GmbH
008     http://www.lat-lon.de
009    
010     This library is free software; you can redistribute it and/or
011     modify it under the terms of the GNU Lesser General Public
012     License as published by the Free Software Foundation; either
013     version 2.1 of the License, or (at your option) any later version.
014     This library is distributed in the hope that it will be useful,
015     but WITHOUT ANY WARRANTY; without even the implied warranty of
016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     Lesser General Public License for more details.
018     You should have received a copy of the GNU Lesser General Public
019     License along with this library; if not, write to the Free Software
020     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021     Contact:
022    
023     Andreas Poth
024     lat/lon GmbH
025     Aennchenstr. 19
026     53177 Bonn
027     Germany
028     E-Mail: poth@lat-lon.de
029    
030     Prof. Dr. Klaus Greve
031     Department of Geography
032     University of Bonn
033     Meckenheimer Allee 166
034     53115 Bonn
035     Germany
036     E-Mail: greve@giub.uni-bonn.de
037     ---------------------------------------------------------------------------*/
038    package org.deegree.crs.utilities;
039    
040    import java.awt.geom.AffineTransform;
041    
042    import javax.vecmath.GMatrix;
043    import javax.vecmath.Matrix3d;
044    
045    import org.deegree.crs.components.Axis;
046    import org.deegree.crs.projections.ProjectionUtils;
047    
048    /**
049     * The <code>Matrix</code> class TODO add documentation here
050     * 
051     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
052     * 
053     * @author last edited by: $Author:$
054     * 
055     * @version $Revision:$, $Date:$
056     * 
057     */
058    
059    public class Matrix extends GMatrix {
060        /**
061         * Serial number for interoperability with different versions.
062         */
063        private static final long serialVersionUID = 3778102551617232269L;
064    
065        /**
066         * Construct a square identity matrix of size <code>size</code>&nbsp;&times;&nbsp;<code>size</code>.
067         * 
068         * @param size
069         */
070        public Matrix( final int size ) {
071            super( size, size );
072        }
073    
074        /**
075         * Construct a matrix of size <code>numRow</code>&nbsp;&times;&nbsp;<code>numCol</code>. Elements on the
076         * diagonal <var>j==i</var> are set to 1.
077         * 
078         * @param numRow
079         * @param numCol
080         */
081        public Matrix( final int numRow, final int numCol ) {
082            super( numRow, numCol );
083        }
084    
085        /**
086         * Constructs a <code>numRow</code>&nbsp;&times;&nbsp;<code>numCol</code> matrix initialized to the values in
087         * the <code>matrix</code> array. The array values are copied in one row at a time in row major fashion. The array
088         * should be exactly <code>numRow*numCol</code> in length. Note that because row and column numbering begins with
089         * zero, <code>row</code> and <code>numCol</code> will be one larger than the maximum possible matrix index
090         * values.
091         * 
092         * @param numRow
093         * @param numCol
094         * @param matrix
095         */
096        public Matrix( final int numRow, final int numCol, final double[] matrix ) {
097            super( numRow, numCol, matrix );
098            if ( numRow * numCol != matrix.length ) {
099                throw new IllegalArgumentException( String.valueOf( matrix.length ) );
100            }
101        }
102    
103        /**
104         * Constructs a new matrix from a two-dimensional array of doubles.
105         * 
106         * @param matrix
107         *            Array of rows. Each row must have the same length.
108         * @throws IllegalArgumentException
109         *             if the specified matrix is not regular (i.e. if all rows doesn't have the same length).
110         */
111        public Matrix( final double[][] matrix ) throws IllegalArgumentException {
112            super( matrix.length, ( matrix.length != 0 ) ? matrix[0].length : 0 );
113            final int numRow = getNumRow();
114            final int numCol = getNumCol();
115            for ( int j = 0; j < numRow; j++ ) {
116                if ( matrix[j].length != numCol ) {
117                    throw new IllegalArgumentException( "Not a regular Matrix (given rows have different lengths)" );
118                }
119                setRow( j, matrix[j] );
120            }
121        }
122    
123        /**
124         * Constructs a new matrix and copies the initial values from the parameter matrix.
125         * 
126         * @param matrix
127         */
128        public Matrix( final GMatrix matrix ) {
129            super( matrix );
130        }
131    
132        /**
133         * Construct a 3&times;3 matrix from the specified affine transform.
134         * 
135         * @param transform
136         */
137        public Matrix( final AffineTransform transform ) {
138            super( 3, 3, new double[] { transform.getScaleX(),
139                                       transform.getShearX(),
140                                       transform.getTranslateX(),
141                                       transform.getShearY(),
142                                       transform.getScaleY(),
143                                       transform.getTranslateY(),
144                                       0,
145                                       0,
146                                       1 } );
147        }
148    
149        /**
150         * Construct an affine transform mapping a source region to a destination region. The regions must have the same
151         * number of dimensions, but their axis order and axis orientation may be different.
152         * 
153         * @param srcRegion
154         *            The source region.
155         * @param srcAxis
156         *            Axis orientation for each dimension of the source region.
157         * @param dstRegion
158         *            The destination region.
159         * @param dstAxis
160         *            Axis orientation for each dimension of the destination region.
161         * @param validRegions
162         *            <code>true</code> if source and destination regions must be taken in account. If <code>false</code>,
163         *            then source and destination regions will be ignored and may be null.
164         */
165        private Matrix( final BBox srcRegion, final Axis[] srcAxis, final BBox dstRegion, final Axis[] dstAxis,
166                        final boolean validRegions ) {
167            this( srcAxis.length + 1 );
168            final int dimension = srcAxis.length;
169            if ( dstAxis.length != dimension ) {
170                throw new IllegalArgumentException( "Given dimensions are of differnt length." );
171            }
172            if ( validRegions ) {
173                if ( dstRegion.getDimension() != dimension || srcRegion.getDimension() != dimension ) {
174                    throw new IllegalArgumentException( "The dimensions of the given regions do not match the axis dimension" );
175                }
176            }
177            /*
178             * Map source axis to destination axis. If no axis is moved (for example if the user want to transform
179             * (NORTH,EAST) to (SOUTH,EAST)), then source and destination index will be equal. If some axis are moved (for
180             * example if the user want to transform (NORTH,EAST) to (EAST,NORTH)), then ordinates at index <code>srcIndex</code>
181             * will have to be moved at index <code>dstIndex</code>.
182             */
183            setZero();
184            for ( int srcIndex = 0; srcIndex < dimension; srcIndex++ ) {
185                boolean hasFound = false;
186                final int srcAxe = srcAxis[srcIndex].getOrientation();
187                final int sourceAxisDirection = Math.abs( srcAxe );
188                //System.out.println( "Source axis: " + srcAxis[srcIndex] );            
189                for ( int dstIndex = 0; dstIndex < dimension; dstIndex++ ) {
190                    final int dstAxeDirection = dstAxis[dstIndex].getOrientation();
191                    //System.out.println( "Dest axis: " + dstAxis[dstIndex] );            
192                    if ( sourceAxisDirection == Math.abs( dstAxeDirection ) ) {
193                        if ( hasFound ) {
194                            throw new IllegalArgumentException( "Following axis are colinear: " + srcAxis[srcIndex].getName()
195                                                                + " dstAxe: "
196                                                                + dstAxis[dstIndex].getName() );
197                        }
198                        hasFound = true;
199                        /*
200                         * Set the matrix elements. Some matrix elements will never be set. They will be left to zero, which
201                         * is their wanted value.
202                         */
203                        final boolean normal = ( srcAxe == dstAxeDirection );
204                        double scale = ( normal ) ? +1 : -1;
205                        double translate = 0;
206                        if ( validRegions ) {
207                            translate = ( normal ) ? dstRegion.getMinimum( dstIndex ) : dstRegion.getMaximum( dstIndex );
208                            scale *= dstRegion.getLength( dstIndex ) / srcRegion.getLength( srcIndex );
209                            translate -= srcRegion.getMinimum( srcIndex ) * scale;
210                        }
211                        setElement( dstIndex, srcIndex, scale );
212                        setElement( dstIndex, dimension, translate );
213                    }
214                }
215                if ( !hasFound ) {
216                    throw new IllegalArgumentException( "No appropriate transformation axis found for srcAxis: " + srcAxis[srcIndex].getName() );
217                }
218            }
219            setElement( dimension, dimension, 1 );
220    
221        }
222    
223        /**
224         * Construct an affine transform changing axis order and/or orientation. For example, the affine transform may
225         * convert (NORTH,WEST) coordinates into (EAST,NORTH). Axis orientation can be inversed only. For example, it is
226         * illegal to transform (NORTH,WEST) coordinates into (NORTH,DOWN).
227         * 
228         * @param srcAxis
229         *            The set of axis orientation for source coordinate system.
230         * @param dstAxis
231         *            The set of axis orientation for destination coordinate system.
232         * @return a Matrix as an affine transform.
233         * @throws IllegalArgumentException
234         *             if the affine transform can't be created for some other raison.
235         */
236        public static Matrix createAffineTransform( final Axis[] srcAxis, final Axis[] dstAxis ) {
237            return new Matrix( null, srcAxis, null, dstAxis, false );
238        }
239    
240        /**
241         * Construct an affine transform that maps a source region to a destination region. Axis order and orientation are
242         * left unchanged.
243         * 
244         * @param srcRegion
245         *            The source region.
246         * @param dstRegion
247         *            The destination region.
248         * @return a Matrix as an affine transform.
249         */
250        public static Matrix createAffineTransform( final BBox srcRegion, final BBox dstRegion ) {
251            final int dimension = srcRegion.getDimension();
252            if ( dstRegion.getDimension() != dimension ) {
253                throw new IllegalArgumentException( "Dimensions do not fit" );
254            }
255    
256            final Matrix matrix = new Matrix( dimension + 1 );
257            for ( int i = 0; i < dimension; i++ ) {
258                final double scale = dstRegion.getLength( i ) / srcRegion.getLength( i );
259                final double translate = dstRegion.getMinimum( i ) - srcRegion.getMinimum( i ) * scale;
260                matrix.setElement( i, i, scale );
261                matrix.setElement( i, dimension, translate );
262            }
263            matrix.setElement( dimension, dimension, 1 );
264            return matrix;
265        }
266    
267        /**
268         * Returns <code>true</code> if this matrix is an affine transform. A transform is affine if the matrix is square
269         * and last row contains only zeros, except in the last column which contains 1.
270         * 
271         * @return <code>true</code> if this matrix is an affine transform.
272         */
273        public final boolean isAffine() {
274            int dimension = getNumRow();
275            if ( dimension != getNumCol() ) {
276                return false;
277            }
278    
279            dimension--;
280            for ( int i = 0; i <= dimension; i++ ) {
281                if ( Math.abs( getElement( dimension, i ) - ( i == dimension ? 1 : 0 ) ) > ProjectionUtils.EPS11 ) {
282                    return false;
283                }
284            }
285            return true;
286        }
287    
288        /**
289         * Copies the first 2x3 values into an affine transform object. If not enough values are available, an identity
290         * transform is returned.
291         * 
292         * @return an affine transform for this matrix. or an identity if this matrix has not sufficient values.
293         * 
294         */
295        public final Matrix3d toAffineTransform() {
296            if ( getNumCol() < 3 || getNumRow() < 2 ) {
297                return new Matrix3d();
298            }
299            return new Matrix3d( getElement( 0, 0 ), getElement( 0, 1 ), getElement( 0,2 ),
300                                 getElement( 1, 0 ), getElement( 1, 1 ), getElement( 1, 2 ),
301                                 0, 0 , 1);
302        }
303    
304        /**
305         * Returns <code>true</code> if this matrix is an identity matrix.
306         * 
307         * @return <code>true</code> if this matrix is an identity matrix.
308         */
309        public final boolean isIdentity() {
310            final int numRow = getNumRow();
311            final int numCol = getNumCol();
312            if ( numRow != numCol ) {
313                return false;
314            }
315            for ( int j = 0; j < numRow; j++ )
316                for ( int i = 0; i < numCol; i++ ) {
317                    if ( Math.abs( getElement( j, i ) - ( i == j ? 1 : 0 ) ) > ProjectionUtils.EPS11 ) {
318                        return false;
319                    }
320                }
321            return true;
322        }
323    }