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 }