001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/ct/MathTransformFactory.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 // OpenGIS dependencies 054 import java.awt.geom.AffineTransform; 055 import java.util.Locale; 056 import java.util.NoSuchElementException; 057 058 import javax.media.jai.ParameterList; 059 060 import org.deegree.model.csct.cs.Projection; 061 import org.deegree.model.csct.pt.Matrix; 062 import org.deegree.model.csct.resources.Naming; 063 import org.deegree.model.csct.resources.WeakHashSet; 064 import org.deegree.model.csct.resources.css.ResourceKeys; 065 import org.deegree.model.csct.resources.css.Resources; 066 067 /** 068 * Creates math transforms. <code>MathTransformFactory</code> is a low level factory that is used 069 * to create {@link MathTransform} objects. Many high level GIS applications will never need to use 070 * a <code>MathTransformFactory</code> directly; they can use a 071 * {@link CoordinateTransformationFactory} instead. However, the <code>MathTransformFactory</code> 072 * class is specified here, since it can be used directly by applications that wish to transform 073 * other types of coordinates (e.g. color coordinates, or image pixel coordinates). <br> 074 * <br> 075 * A math transform is an object that actually does the work of applying formulae to coordinate 076 * values. The math transform does not know or care how the coordinates relate to positions in the 077 * real world. This lack of semantics makes implementing <code>MathTransformFactory</code> 078 * significantly easier than it would be otherwise. 079 * 080 * For example <code>MathTransformFactory</code> can create affine math transforms. The affine 081 * transform applies a matrix to the coordinates without knowing how what it is doing relates to the 082 * real world. So if the matrix scales <var>Z</var> values by a factor of 1000, then it could be 083 * converting meters into millimeters, or it could be converting kilometers into meters. <br> 084 * <br> 085 * Because math transforms have low semantic value (but high mathematical value), programmers who do 086 * not have much knowledge of how GIS applications use coordinate systems, or how those coordinate 087 * systems relate to the real world can implement <code>MathTransformFactory</code>. 088 * 089 * The low semantic content of math transforms also means that they will be useful in applications 090 * that have nothing to do with GIS coordinates. For example, a math transform could be used to map 091 * color coordinates between different color spaces, such as converting (red, green, blue) colors 092 * into (hue, light, saturation) colors. <br> 093 * <br> 094 * Since a math transform does not know what its source and target coordinate systems mean, it is 095 * not necessary or desirable for a math transform object to keep information on its source and 096 * target coordinate systems. 097 * 098 * @version 1.00 099 * @author OpenGIS (www.opengis.org) 100 * @author Martin Desruisseaux 101 * 102 * @author last edited by: $Author: bezema $ 103 * 104 * @version $Revision: 6259 $, $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 2007) $ 105 * 106 * @see "org.opengis.ct.CT_MathTransformFactory" 107 */ 108 public class MathTransformFactory { 109 /** 110 * The default math transform factory. This factory will be constructed only when first needed. 111 */ 112 private static MathTransformFactory DEFAULT; 113 114 /** 115 * A pool of math transform. This pool is used in order to returns instance of existing math 116 * transforms when possible. 117 */ 118 static final WeakHashSet pool = new WeakHashSet(); 119 120 /** 121 * List of registered math transforms. 122 */ 123 private final MathTransformProvider[] providers; 124 125 /** 126 * Construct a factory using the specified providers. 127 * 128 * @param providers 129 */ 130 public MathTransformFactory( final MathTransformProvider[] providers ) { 131 this.providers = providers.clone(); 132 } 133 134 /** 135 * Returns the default math transform factory. 136 * 137 * @return the default math transform factory. 138 */ 139 public static synchronized MathTransformFactory getDefault() { 140 if ( DEFAULT == null ) { 141 DEFAULT = new MathTransformFactory( 142 new MathTransformProvider[] { 143 new MercatorProjection.Provider(), 144 new LambertConformalProjection.Provider(), 145 new StereographicProjection.Provider(), // Automatic 146 new StereographicProjection.Provider( 147 true ), // Polar 148 new StereographicProjection.Provider( 149 false ), // Oblique 150 new TransverseMercatorProjection.Provider( 151 false ), // Universal 152 new TransverseMercatorProjection.Provider( 153 true ), // Modified 154 new GeocentricTransform.Provider( 155 false ), // Geographic 156 // to 157 // Geocentric 158 new GeocentricTransform.Provider( 159 true ) // Geocentric 160 // to 161 // Geographic 162 } ); 163 for ( int i = DEFAULT.providers.length; --i >= 0; ) { 164 final MathTransformProvider provider = DEFAULT.providers[i]; 165 if ( provider instanceof MapProjection.Provider ) { 166 // Register only projections. 167 Naming.PROJECTIONS.bind( provider.getClassName(), 168 provider.getParameterListDescriptor() ); 169 } 170 } 171 } 172 return DEFAULT; 173 } 174 175 /** 176 * Creates an identity transform of the specified dimension. 177 * 178 * @param dimension 179 * The source and target dimension. 180 * @return The identity transform. 181 */ 182 public MathTransform createIdentityTransform( final int dimension ) { 183 // Affine transform has one more row/column than dimension. 184 return createAffineTransform( new Matrix( dimension + 1 ) ); 185 } 186 187 /** 188 * Creates an affine transform from a matrix. 189 * 190 * @param matrix 191 * The matrix used to define the affine transform. 192 * @return The affine transform. 193 */ 194 public MathTransform2D createAffineTransform( final AffineTransform matrix ) { 195 return (MathTransform2D) pool.intern( new AffineTransform2D( matrix ) ); 196 } 197 198 /** 199 * Creates an affine transform from a matrix. 200 * 201 * @param matrix 202 * The matrix used to define the affine transform. 203 * @return The affine transform. 204 * 205 */ 206 public MathTransform createAffineTransform( final Matrix matrix ) { 207 /* 208 * If the user is requesting a 2D transform, delegate to the highly optimized 209 * java.awt.geom.AffineTransform class. 210 */ 211 if ( matrix.getNumRow() == 3 && matrix.isAffine() ) // Affine transform are square. 212 { 213 return createAffineTransform( matrix.toAffineTransform2D() ); 214 } 215 /* 216 * General case (slower). May not be a real affine transform. We accept it anyway... 217 */ 218 return (MathTransform) pool.intern( new MatrixTransform( matrix ) ); 219 } 220 221 /** 222 * Returns the underlying matrix for the specified transform, or <code>null</code> if the 223 * matrix is unavailable. 224 */ 225 private static Matrix getMatrix( final MathTransform transform ) { 226 if ( transform instanceof AffineTransform ) 227 return new Matrix( (AffineTransform) transform ); 228 if ( transform instanceof MatrixTransform ) 229 return ( (MatrixTransform) transform ).getMatrix(); 230 return null; 231 } 232 233 /** 234 * Tests if one math transform is the inverse of the other. This implementation can't detect 235 * every case. It just detect the case when <code>tr2</code> is an instance of 236 * {@link AbstractMathTransform.Inverse}. 237 */ 238 private static boolean areInverse( final MathTransform tr1, final MathTransform tr2 ) { 239 if ( tr2 instanceof AbstractMathTransform.Inverse ) { 240 return tr1.equals( ( (AbstractMathTransform.Inverse) tr2 ).inverse() ); 241 // TODO: we could make this test more general (just compare with tr2.inverse(), 242 // no matter if it is an instance of AbstractMathTransform.Inverse or not, 243 // and catch the exception if one is thrown). Would it be too expensive to 244 // create inconditionnaly the inverse transform? 245 } 246 return false; 247 } 248 249 /** 250 * Creates a transform by concatenating two existing transforms. A concatenated transform acts 251 * in the same way as applying two transforms, one after the other. The dimension of the output 252 * space of the first transform must match the dimension of the input space in the second 253 * transform. If you wish to concatenate more than two transforms, then you can repeatedly use 254 * this method. 255 * 256 * @param tr1 257 * The first transform to apply to points. 258 * @param tr2 259 * The second transform to apply to points. 260 * @return The concatenated transform. 261 * 262 */ 263 public MathTransform createConcatenatedTransform( MathTransform tr1, MathTransform tr2 ) { 264 if ( tr1.isIdentity() ) 265 return tr2; 266 if ( tr2.isIdentity() ) 267 return tr1; 268 /* 269 * If both transforms use matrix, then we can create a single transform using the concatened 270 * matrix. 271 */ 272 final Matrix matrix1 = getMatrix( tr1 ); 273 if ( matrix1 != null ) { 274 final Matrix matrix2 = getMatrix( tr2 ); 275 if ( matrix2 != null ) { 276 // May not be really affine, but work anyway... 277 // This call will detect and optimize the special 278 // case where an 'AffineTransform' can be used. 279 matrix2.mul( matrix1 ); 280 return createAffineTransform( matrix2 ); 281 } 282 } 283 /* 284 * If one transform is the inverse of the other, returns the identity transform. 285 */ 286 if ( areInverse( tr1, tr2 ) || areInverse( tr2, tr1 ) ) { 287 return createIdentityTransform( tr1.getDimSource() ); 288 } 289 /* 290 * If one or both math transform are instance of {@link ConcatenedTransform}, then maybe it 291 * is possible to efficiently concatenate <code>tr1</code> or <code>tr2</code> with one 292 * of step transforms. Try that... 293 */ 294 if ( tr1 instanceof ConcatenedTransform ) { 295 final ConcatenedTransform ctr = (ConcatenedTransform) tr1; 296 tr1 = ctr.transform1; 297 tr2 = createConcatenatedTransform( ctr.transform2, tr2 ); 298 } 299 if ( tr2 instanceof ConcatenedTransform ) { 300 final ConcatenedTransform ctr = (ConcatenedTransform) tr2; 301 tr1 = createConcatenatedTransform( tr1, ctr.transform1 ); 302 tr2 = ctr.transform2; 303 } 304 /* 305 * The returned transform will implements {@link MathTransform2D} if source and target 306 * dimensions are equal to 2. {@link MathTransform} implementations are available in two 307 * version: direct and non-direct. The "non-direct" version use an intermediate buffer when 308 * performing transformations; they are slower and consume more memory. They are used only 309 * as a fallback when a "direct" version can't be created. 310 */ 311 final MathTransform transform; 312 final int dimSource = tr1.getDimSource(); 313 final int dimTarget = tr2.getDimTarget(); 314 if ( dimSource == 2 && dimTarget == 2 ) { 315 if ( tr1 instanceof MathTransform2D && tr2 instanceof MathTransform2D ) { 316 transform = new ConcatenedTransformDirect2D( this, (MathTransform2D) tr1, 317 (MathTransform2D) tr2 ); 318 } else 319 transform = new ConcatenedTransform2D( this, tr1, tr2 ); 320 } else if ( dimSource == tr1.getDimTarget() && tr2.getDimSource() == dimTarget ) { 321 transform = new ConcatenedTransformDirect( this, tr1, tr2 ); 322 } else 323 transform = new ConcatenedTransform( this, tr1, tr2 ); 324 return (MathTransform) pool.intern( transform ); 325 } 326 327 /** 328 * Creates a transform which passes through a subset of ordinates to another transform. This 329 * allows transforms to operate on a subset of ordinates. For example, if you have (<var>latitidue</var>,<var>longitude</var>,<var>height</var>) 330 * coordinates, then you may wish to convert the height values from feet to meters without 331 * affecting the latitude and longitude values. 332 * 333 * @param firstAffectedOrdinate 334 * Index of the first affected ordinate. 335 * @param subTransform 336 * The sub transform. 337 * @param numTrailingOrdinates 338 * Number of trailing ordinates to pass through. Affected ordinates will range from 339 * <code>firstAffectedOrdinate</code> inclusive to 340 * <code>dimTarget-numTrailingOrdinates</code> exclusive. 341 * @return A pass through transform with the following dimensions:<br> 342 * 343 * <pre> 344 * Source: firstAffectedOrdinate + subTransform.getDimSource() + numTrailingOrdinates 345 * Target: firstAffectedOrdinate + subTransform.getDimTarget() + numTrailingOrdinates 346 * </pre> 347 * 348 */ 349 public MathTransform createPassThroughTransform( final int firstAffectedOrdinate, 350 final MathTransform subTransform, 351 final int numTrailingOrdinates ) { 352 if ( firstAffectedOrdinate < 0 ) { 353 throw new IllegalArgumentException( 354 Resources.format( 355 ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2, 356 "firstAffectedOrdinate", 357 new Integer( 358 firstAffectedOrdinate ) ) ); 359 } 360 if ( numTrailingOrdinates < 0 ) { 361 throw new IllegalArgumentException( 362 Resources.format( 363 ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2, 364 "numTrailingOrdinates", 365 new Integer( numTrailingOrdinates ) ) ); 366 } 367 if ( firstAffectedOrdinate == 0 && numTrailingOrdinates == 0 ) { 368 return subTransform; 369 } 370 // 371 // Optimize the "Identity transform" case. 372 // 373 if ( subTransform.isIdentity() ) { 374 final int dimension = subTransform.getDimSource(); 375 if ( dimension == subTransform.getDimTarget() ) { 376 // The AffineTransform is easier to concatenate with other transforms. 377 return createIdentityTransform( firstAffectedOrdinate + dimension 378 + numTrailingOrdinates ); 379 } 380 } 381 // 382 // Optimize the "Pass through case": this is done 383 // right into PassThroughTransform's constructor. 384 // 385 return (MathTransform) pool.intern( new PassThroughTransform( firstAffectedOrdinate, 386 subTransform, 387 numTrailingOrdinates ) ); 388 } 389 390 /** 391 * Creates a transform which retains only a portion of an other transform. For example if the 392 * source coordinate system has (<var>longitude</var>, <var>latitude</var>, <var>height</var>) 393 * values, then a sub-transform may be used to keep only the (<var>longitude</var>, 394 * <var>latitude</var>) part. In most cases, the created sub-transform is non-invertible since 395 * it loose informations. <br> 396 * <br> 397 * This transform is a special case of a non-square matrix transform with less rows than 398 * columns. However, using a <code>createSubMathTransfom(...)</code> method makes it easier to 399 * optimize some common cases. 400 * 401 * @param transform 402 * The transform. 403 * @param lower 404 * Index of the first ordinate to keep. 405 * @param upper 406 * Index of the first ordinate. Must be greater than <code>lower</code>. 407 * @return 408 */ 409 public MathTransform createSubMathTransform( final int lower, final int upper, 410 final MathTransform transform ) { 411 if ( lower < 0 || lower >= upper ) { 412 throw new IllegalArgumentException( 413 Resources.format( 414 ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2, 415 "lower", new Integer( lower ) ) ); 416 } 417 final int dimTarget = transform.getDimTarget(); 418 if ( upper > dimTarget ) { 419 throw new IllegalArgumentException( 420 Resources.format( 421 ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2, 422 "upper", new Integer( upper ) ) ); 423 } 424 if ( lower == 0 && upper == dimTarget ) { 425 return transform; 426 } 427 if ( transform instanceof PassThroughTransform ) { 428 // Special case for pass through transform: 429 // Compute lower and upper values relatives 430 // to the underlying sub-transform. 431 final PassThroughTransform passThrough = (PassThroughTransform) transform; 432 final int lowerTr = lower - passThrough.firstAffectedOrdinate; 433 final int upperTr = upper - passThrough.firstAffectedOrdinate; 434 final int passDim = passThrough.transform.getDimTarget(); 435 if ( lowerTr >= 0 && upperTr <= passDim ) { 436 return createSubMathTransform( lowerTr, upperTr, passThrough.transform ); 437 } 438 if ( lowerTr <= 0 && upperTr >= passDim ) { 439 return createPassThroughTransform( -lowerTr, passThrough.transform, upperTr 440 - passDim ); 441 } 442 } 443 // General case: use a matrix. 444 final int dimOutput = upper - lower; 445 final Matrix matrix = new Matrix( dimOutput + 1, dimTarget + 1 ); 446 matrix.setZero(); 447 for ( int i = lower; i < upper; i++ ) { 448 matrix.setElement( i - lower, i, 1 ); 449 } 450 matrix.setElement( dimOutput, dimTarget, 1 ); // Affine transform has one more row/column 451 // than dimension. 452 return createConcatenatedTransform( transform, createAffineTransform( matrix ) ); 453 } 454 455 /** 456 * Creates a transform from a classification name and parameters. The client must ensure that 457 * all the linear parameters are expressed in meters, and all the angular parameters are 458 * expressed in degrees. Also, they must supply "semi_major" and "semi_minor" parameters for 459 * cartographic projection transforms. 460 * 461 * @param classification 462 * The classification name of the transform (e.g. "Transverse_Mercator"). Leading and 463 * trailing spaces are ignored, and comparaison is case-insensitive. 464 * @param parameters 465 * The parameter values in standard units. 466 * @return The parameterized transform. 467 * @throws NoSuchElementException 468 * if there is no transform for the specified classification. 469 * @throws MissingParameterException 470 * if a parameter was required but not found. 471 * 472 */ 473 public MathTransform createParameterizedTransform( String classification, 474 final ParameterList parameters ) 475 throws NoSuchElementException, MissingParameterException { 476 final MathTransform transform; 477 classification = classification.trim(); 478 if ( classification.equalsIgnoreCase( "Affine" ) ) { 479 // Special case for "Affine", since the ParameterListDescriptor 480 // depends of the matrix size. 481 transform = MatrixTransform.Provider.staticCreate( parameters ); 482 } else { 483 transform = getMathTransformProvider( classification ).create( parameters ); 484 } 485 return (MathTransform) pool.intern( transform ); 486 } 487 488 /** 489 * Convenience method for creating a transform from a projection. 490 * 491 * @param projection 492 * The projection. 493 * @return The parameterized transform. 494 * @throws NoSuchElementException 495 * if there is no transform for the specified projection. 496 * @throws MissingParameterException 497 * if a parameter was required but not found. 498 */ 499 public MathTransform createParameterizedTransform( final Projection projection ) 500 throws NoSuchElementException, MissingParameterException { 501 return createParameterizedTransform( projection.getClassName(), projection.getParameters() ); 502 } 503 504 /** 505 * Returns the classification names of every available transforms. The returned array may have a 506 * zero length, but will never be null. 507 * 508 * @return the classification names of every available transforms. The returned array may have a 509 * zero length, but will never be <code>null</code>. 510 * 511 */ 512 public String[] getAvailableTransforms() { 513 final String[] names = new String[providers.length + 1]; 514 int i; 515 for ( i = 0; i < names.length; i++ ) { 516 names[i] = providers[i].getClassName(); 517 } 518 // Special case for "Affine", since the ParameterListDescriptor 519 // depends of the matrix size. 520 names[i] = "Affine"; 521 return names; 522 } 523 524 /** 525 * Returns the provider for the specified classification. This provider may be used to query 526 * parameter list for a classification name (e.g. 527 * <code>getMathTransformProvider("Transverse_Mercator").getParameterList()</code>), or the 528 * transform name in a given locale (e.g. 529 * <code>getMathTransformProvider("Transverse_Mercator").getName({@link Locale#FRENCH})</code>) 530 * 531 * @param classification 532 * The classification name of the transform (e.g. "Transverse_Mercator"). It should 533 * be one of the name returned by {@link #getAvailableTransforms}. Leading and 534 * trailing spaces are ignored. Comparisons are case-insensitive. 535 * @return The provider for a math transform. 536 * @throws NoSuchElementException 537 * if there is no provider registered with the specified classification name. 538 */ 539 public MathTransformProvider getMathTransformProvider( String classification ) 540 throws NoSuchElementException { 541 classification = classification.trim(); 542 for ( int i = 0; i < providers.length; i++ ) { 543 if ( classification.equalsIgnoreCase( providers[i].getClassName().trim() ) ) 544 return providers[i]; 545 } 546 throw new NoSuchElementException( 547 Resources.format( 548 ResourceKeys.ERROR_NO_TRANSFORM_FOR_CLASSIFICATION_$1, 549 classification ) ); 550 } 551 552 /** 553 * Create a provider for affine transforms of the specified dimension. Created affine transforms 554 * will have a size of <code>numRow × numCol</code>. <br> 555 * <br> 556 * <table align="center" border='1' cellpadding='3' bgcolor="F4F8FF"> 557 * <tr bgcolor="#B9DCFF"> 558 * <th>Parameter</th> 559 * <th>Description</th> 560 * </tr> 561 * <tr> 562 * <td><code>Num_row</code></td> 563 * <td>Number of rows in matrix</td> 564 * </tr> 565 * <tr> 566 * <td><code>Num_col</code></td> 567 * <td>Number of columns in matrix</td> 568 * </tr> 569 * <tr> 570 * <td><code>elt_<r>_<c></code></td> 571 * <td>Element of matrix</td> 572 * </tr> 573 * </table> <br> 574 * For the element parameters, <code><r></code> and <code><c></code> should be 575 * substituted by printed decimal numbers. The values of <var>r</var> should be from 0 to 576 * <code>(num_row-1)</code>, and the values of <var>c</var> should be from 0 to 577 * <code>(num_col-1)</code>. Any undefined matrix elements are assumed to be zero for 578 * <code>(r!=c)</code>, and one for <code>(r==c)</code>. This corresponds to the identity 579 * transformation when the number of rows and columns are the same. The number of columns 580 * corresponds to one more than the dimension of the source coordinates and the number of rows 581 * corresponds to one more than the dimension of target coordinates. The extra dimension in the 582 * matrix is used to let the affine map do a translation. 583 * 584 * @param numRow 585 * The number of matrix's rows. 586 * @param numCol 587 * The number of matrix's columns. 588 * @return The provider for an affine transform. 589 * @throws IllegalArgumentException 590 * if <code>numRow</code> or <code>numCol</code> is not a positive number. 591 */ 592 public MathTransformProvider getAffineTransformProvider( final int numRow, final int numCol ) 593 throws IllegalArgumentException { 594 return new MatrixTransform.Provider( numRow, numCol ); 595 } 596 597 }