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> × <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> × <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> × <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×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 }