001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/ct/AbstractMathTransform.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.Shape; 055 import java.awt.geom.AffineTransform; 056 import java.awt.geom.GeneralPath; 057 import java.awt.geom.IllegalPathStateException; 058 import java.awt.geom.Line2D; 059 import java.awt.geom.PathIterator; 060 import java.awt.geom.Point2D; 061 import java.awt.geom.QuadCurve2D; 062 063 import javax.vecmath.SingularMatrixException; 064 065 import org.deegree.model.csct.pt.CoordinatePoint; 066 import org.deegree.model.csct.pt.Matrix; 067 import org.deegree.model.csct.pt.MismatchedDimensionException; 068 import org.deegree.model.csct.resources.Geometry; 069 import org.deegree.model.csct.resources.Utilities; 070 import org.deegree.model.csct.resources.css.ResourceKeys; 071 import org.deegree.model.csct.resources.css.Resources; 072 073 /** 074 * Provides a default implementations for most methods required by the 075 * {@link MathTransform} interface. <code>AbstractMathTransform</code> 076 * provides a convenient base class from which other transform classes 077 * can be easily derived. In addition, <code>AbstractMathTransform</code> 078 * implements methods required by the {@link MathTransform2D} interface, 079 * but <strong>does not</strong> implements <code>MathTransform2D</code>. 080 * Subclasses must declare <code>implements MathTransform2D</code> 081 * themself if they know to maps two-dimensional coordinate systems. 082 * 083 * @version 1.0 084 * @author Martin Desruisseaux 085 */ 086 public abstract class AbstractMathTransform implements MathTransform { 087 /** 088 * Construct a math transform. 089 */ 090 public AbstractMathTransform() { 091 } 092 093 /** 094 * Returns a human readable name, if available. If no name is available in 095 * the specified locale, then this method returns a name in an arbitrary 096 * locale. If no name is available in any locale, then this method returns 097 * <code>null</code>. The default implementation always returns <code>null</code>. 098 * 099 * @param locale The desired locale, or <code>null</code> for a default locale. 100 * @return The transform name localized in the specified locale if possible, or 101 * <code>null</code> if no name is available in any locale. 102 */ 103 protected String getName( ) { 104 return null; 105 } 106 107 /** 108 * Transforms the specified <code>ptSrc</code> and stores the result in <code>ptDst</code>. 109 * The default implementation invokes {@link #transform(double[],int,double[],int,int)} 110 * using a temporary array of doubles. 111 * 112 * @param ptSrc the specified coordinate point to be transformed. 113 * @param ptDst the specified coordinate point that stores the 114 * result of transforming <code>ptSrc</code>, or 115 * <code>null</code>. 116 * @return the coordinate point after transforming <code>ptSrc</code> 117 * and stroring the result in <code>ptDst</code>. 118 * @throws MismatchedDimensionException if this transform 119 * doesn't map two-dimensional coordinate systems. 120 * @throws TransformException if the point can't be transformed. 121 * 122 * @see MathTransform2D#transform(Point2D,Point2D) 123 */ 124 public Point2D transform( final Point2D ptSrc, final Point2D ptDst ) 125 throws TransformException { 126 if ( getDimSource() != 2 || getDimTarget() != 2 ) { 127 throw new MismatchedDimensionException(); 128 } 129 final double[] ord = new double[] { ptSrc.getX(), ptSrc.getY() }; 130 this.transform( ord, 0, ord, 0, 1 ); 131 if ( ptDst != null ) { 132 ptDst.setLocation( ord[0], ord[1] ); 133 return ptDst; 134 } 135 return new Point2D.Double( ord[0], ord[1] ); 136 } 137 138 /** 139 * Transforms the specified <code>ptSrc</code> and stores the result 140 * in <code>ptDst</code>. The default implementation invokes 141 * {@link #transform(double[],int,double[],int,int)}. 142 */ 143 public CoordinatePoint transform( final CoordinatePoint ptSrc, CoordinatePoint ptDst ) 144 throws TransformException { 145 final int pointDim = ptSrc.getDimension(); 146 final int sourceDim = getDimSource(); 147 final int targetDim = getDimTarget(); 148 if ( pointDim != sourceDim ) { 149 throw new MismatchedDimensionException( pointDim, sourceDim ); 150 } 151 if ( ptDst == null ) { 152 ptDst = new CoordinatePoint( targetDim ); 153 } else if ( ptDst.getDimension() != targetDim ) { 154 throw new MismatchedDimensionException( ptDst.getDimension(), targetDim ); 155 } 156 transform( ptSrc.ord, 0, ptDst.ord, 0, 1 ); 157 return ptDst; 158 } 159 160 /** 161 * Transforms a list of coordinate point ordinal values. The default implementation 162 * invokes {@link #transform(double[],int,double[],int,int)} using a temporary array 163 * of doubles. 164 */ 165 public void transform( final float[] srcPts, final int srcOff, final float[] dstPts, 166 final int dstOff, final int numPts ) 167 throws TransformException { 168 final int dimSource = getDimSource(); 169 final int dimTarget = getDimTarget(); 170 final double[] tmpPts = new double[numPts * Math.max( dimSource, dimTarget )]; 171 for ( int i = numPts * dimSource; --i >= 0; ) 172 tmpPts[i] = srcPts[srcOff + i]; 173 transform( tmpPts, 0, tmpPts, 0, numPts ); 174 for ( int i = numPts * dimTarget; --i >= 0; ) 175 dstPts[dstOff + i] = (float) tmpPts[i]; 176 } 177 178 /** 179 * Transform the specified shape. The default implementation compute 180 * quadratic curves using three points for each shape's segments. 181 * 182 * @param shape Shape to transform. 183 * @return Transformed shape, or <code>shape</code> if 184 * this transform is the identity transform. 185 * @throws IllegalStateException if this transform doesn't map 2D coordinate systems. 186 * @throws TransformException if a transform failed. 187 * 188 * @see MathTransform2D#createTransformedShape(Shape) 189 */ 190 public Shape createTransformedShape( final Shape shape ) 191 throws TransformException { 192 return isIdentity() ? shape : createTransformedShape( shape, null, null, Geometry.PARALLEL ); 193 } 194 195 /** 196 * Transforme une forme g�om�trique. Cette m�thode copie toujours les coordonn�es 197 * transform�es dans un nouvel objet. La plupart du temps, elle produira un objet 198 * {@link GeneralPath}. Elle peut aussi retourner des objets {@link Line2D} ou 199 * {@link QuadCurve2D} si une telle simplification est possible. 200 * 201 * @param shape Forme g�om�trique � transformer. 202 * @param preTr Transformation affine � appliquer <em>avant</em> de transformer la forme 203 * <code>shape</code>, ou <code>null</code> pour ne pas en appliquer. 204 * Cet argument sera surtout utile lors des transformations inverses. 205 * @param postTr Transformation affine � appliquer <em>apr�s</em> avoir transform�e la 206 * forme <code>shape</code>, ou <code>null</code> pour ne pas en appliquer. 207 * Cet argument sera surtout utile lors des transformations directes. 208 * @param quadDir Direction des courbes quadratiques ({@link Geometry#HORIZONTAL} 209 * ou {@link Geometry#PARALLEL}). 210 * 211 * @return La forme g�om�trique transform�e. 212 * @throws MismatchedDimensionException if this transform 213 * doesn't map two-dimensional coordinate systems. 214 * @throws TransformException Si une transformation a �chou�. 215 */ 216 final Shape createTransformedShape( final Shape shape, final AffineTransform preTr, 217 final AffineTransform postTr, final int quadDir ) 218 throws TransformException { 219 if ( getDimSource() != 2 || getDimTarget() != 2 ) { 220 throw new MismatchedDimensionException(); 221 } 222 final PathIterator it = shape.getPathIterator( preTr ); 223 final GeneralPath path = new GeneralPath( it.getWindingRule() ); 224 final Point2D.Float ctrl = new Point2D.Float(); 225 final double[] buffer = new double[6]; 226 227 double ax = 0, ay = 0; // Coordonn�es du dernier point avant la projection. 228 double px = 0, py = 0; // Coordonn�es du dernier point apr�s la projection. 229 int indexCtrlPt = 0; // Index du point de contr�le dans 'buffer'. 230 int indexLastPt = 0; // Index du dernier point dans 'buffer'. 231 for ( ; !it.isDone(); it.next() ) { 232 switch ( it.currentSegment( buffer ) ) { 233 default: { 234 throw new IllegalPathStateException(); 235 } 236 case PathIterator.SEG_CLOSE: { 237 /* 238 * Ferme la forme g�om�trique, puis continue la boucle. On utilise une 239 * instruction 'continue' plut�t que 'break' car il ne faut pas ex�cuter 240 * le code qui suit ce 'switch'. 241 */ 242 path.closePath(); 243 continue; 244 } 245 case PathIterator.SEG_MOVETO: { 246 /* 247 * M�morise les coordonn�es sp�cifi�es (avant et apr�s les avoir 248 * projet�es), puis continue la boucle. On utilise une instruction 249 * 'continue' plut�t que 'break' car il ne faut pas ex�cuter le 250 * code qui suit ce 'switch'. 251 */ 252 ax = buffer[0]; 253 ay = buffer[1]; 254 transform( buffer, 0, buffer, 0, 1 ); 255 path.moveTo( (float) ( px = buffer[0] ), (float) ( py = buffer[1] ) ); 256 continue; 257 } 258 case PathIterator.SEG_LINETO: { 259 /* 260 * Place dans 'buffer[2,3]' les coordonn�es 261 * d'un point qui se trouve sur la droite: 262 * 263 * x = 0.5*(x1+x2) 264 * y = 0.5*(y1+y2) 265 * 266 * Ce point sera trait� apr�s le 'switch', d'o� 267 * l'utilisation d'un 'break' plut�t que 'continue'. 268 */ 269 indexLastPt = 0; 270 indexCtrlPt = 2; 271 buffer[2] = 0.5 * ( ax + ( ax = buffer[0] ) ); 272 buffer[3] = 0.5 * ( ay + ( ay = buffer[1] ) ); 273 break; 274 } 275 case PathIterator.SEG_QUADTO: { 276 /* 277 * Place dans 'buffer[0,1]' les coordonn�es 278 * d'un point qui se trouve sur la courbe: 279 * 280 * x = 0.5*(ctrlx + 0.5*(x1+x2)) 281 * y = 0.5*(ctrly + 0.5*(y1+y2)) 282 * 283 * Ce point sera trait� apr�s le 'switch', d'o� 284 * l'utilisation d'un 'break' plut�t que 'continue'. 285 */ 286 indexLastPt = 2; 287 indexCtrlPt = 0; 288 buffer[0] = 0.5 * ( buffer[0] + 0.5 * ( ax + ( ax = buffer[2] ) ) ); 289 buffer[1] = 0.5 * ( buffer[1] + 0.5 * ( ay + ( ay = buffer[3] ) ) ); 290 break; 291 } 292 case PathIterator.SEG_CUBICTO: { 293 /* 294 * Place dans 'buffer[0,1]' les coordonn�es 295 * d'un point qui se trouve sur la courbe: 296 * 297 * x = 0.25*(1.5*(ctrlx1+ctrlx2) + 0.5*(x1+x2)); 298 * y = 0.25*(1.5*(ctrly1+ctrly2) + 0.5*(y1+y2)); 299 * 300 * Ce point sera trait� apr�s le 'switch', d'o� 301 * l'utilisation d'un 'break' plut�t que 'continue'. 302 * 303 * NOTE: Le point calcul� est bien sur la courbe, mais n'est pas n�cessairement repr�sentatif. 304 * Cet algorithme remplace les deux points de contr�les par un seul, ce qui se traduit 305 * par une perte de souplesse qui peut donner de mauvais r�sultats si la courbe cubique 306 * �tait bien tordue. Projeter une courbe cubique ne me semble pas �tre un probl�me simple, 307 * mais heureusement ce cas devrait �tre assez rare. Il se produira le plus souvent si on 308 * essaye de projeter un cercle ou une ellipse, auxquels cas l'algorithme actuel donnera 309 * quand m�me des r�sultats tol�rables. 310 */ 311 indexLastPt = 4; 312 indexCtrlPt = 0; 313 buffer[0] = 0.25 * ( 1.5 * ( buffer[0] + buffer[2] ) + 0.5 * ( ax + ( ax = buffer[4] ) ) ); 314 buffer[1] = 0.25 * ( 1.5 * ( buffer[1] + buffer[3] ) + 0.5 * ( ay + ( ay = buffer[5] ) ) ); 315 break; 316 } 317 } 318 /* 319 * Applique la transformation sur les points qui se 320 * trouve dans le buffer, puis ajoute ces points � 321 * la forme g�om�trique projet�e comme une courbe 322 * quadratique. 323 */ 324 transform( buffer, 0, buffer, 0, 2 ); 325 if ( Geometry.parabolicControlPoint( px, py, buffer[indexCtrlPt], 326 buffer[indexCtrlPt + 1], buffer[indexLastPt], 327 buffer[indexLastPt + 1], quadDir, ctrl ) != null ) { 328 path.quadTo( ctrl.x, ctrl.y, (float) ( px = buffer[indexLastPt + 0] ), 329 (float) ( py = buffer[indexLastPt + 1] ) ); 330 } else 331 path.lineTo( (float) ( px = buffer[indexLastPt + 0] ), 332 (float) ( py = buffer[indexLastPt + 1] ) ); 333 } 334 /* 335 * La projection de la forme g�om�trique est termin�e. Applique 336 * une transformation affine si c'�tait demand�e, puis retourne 337 * une version si possible simplifi�e de la forme g�om�trique. 338 */ 339 if ( postTr != null ) { 340 path.transform( postTr ); 341 } 342 return Geometry.toPrimitive( path ); 343 } 344 345 /** 346 * Gets the derivative of this transform at a point. The default 347 * implementation invokes {@link #derivative(CoordinatePoint)}. 348 * 349 * @param point The coordinate point where to evaluate the derivative. 350 * @return The derivative at the specified point as a 2x2 matrix. 351 * @throws MismatchedDimensionException if the input dimension is not 2. 352 * @throws TransformException if the derivative can't be evaluated at the specified point. 353 * 354 * @see MathTransform2D#derivative(Point2D) 355 */ 356 public Matrix derivative( final Point2D point ) 357 throws TransformException { 358 return derivative( new CoordinatePoint( point ) ); 359 } 360 361 /** 362 * Gets the derivative of this transform at a point. The default 363 * implementation throws an {@link UnsupportedOperationException} 364 * <strong>(note: this default implementation may change in a future 365 * version)</strong>. 366 * 367 * @param point The coordinate point where to evaluate the derivative. 368 * @return The derivative at the specified point (never <code>null</code>). 369 * @throws TransformException if the derivative can't be evaluated at the specified point. 370 */ 371 public Matrix derivative( final CoordinatePoint point ) 372 throws TransformException { 373 throw new UnsupportedOperationException( "Matrix derivative not yet implemented" ); 374 } 375 376 /** 377 * Creates the inverse transform of this object. 378 * The default implementation returns <code>this</code> if this transform is an identity 379 * transform, and throws a {@link NoninvertibleTransformException} otherwise. Subclasses 380 * should override this method. 381 */ 382 public MathTransform inverse() 383 throws NoninvertibleTransformException { 384 if ( isIdentity() ) 385 return this; 386 throw new NoninvertibleTransformException( 387 Resources.format( ResourceKeys.ERROR_NONINVERTIBLE_TRANSFORM ) ); 388 } 389 390 /** 391 * Returns a hash value for this transform. 392 */ 393 public int hashCode() { 394 return getDimSource() + 37 * getDimTarget(); 395 } 396 397 /** 398 * Compares the specified object with this math transform for equality. 399 * The default implementation checks if <code>object</code> is an instance 400 * of the same class than <code>this</code>. Subclasses should override 401 * this method in order to compare internal fields. 402 */ 403 public boolean equals( final Object object ) { 404 // Do not check 'object==this' here, since this 405 // optimization is usually done in subclasses. 406 return ( object != null && getClass().equals( object.getClass() ) ); 407 } 408 409 /** 410 * Returns a string repr�sentation of this transform. 411 * Subclasses should override this method in order to 412 * returns Well Know Text (WKT) instead. 413 */ 414 public String toString() { 415 final StringBuffer buffer = new StringBuffer( Utilities.getShortClassName( this ) ); 416 buffer.append( '[' ); 417 buffer.append( getDimSource() ); 418 buffer.append( "D \u2192 " ); // Arrow --> 419 buffer.append( getDimTarget() ); 420 buffer.append( "D]" ); 421 return buffer.toString(); 422 } 423 424 /** 425 * Returns a string buffer initialized with "PARAM_MT" 426 * and a classification name. This is a convenience 427 * method for WKT formatting. 428 */ 429 static StringBuffer paramMT( final String classification ) { 430 final StringBuffer buffer = new StringBuffer( "PARAM_MT[\"" ); 431 buffer.append( classification ); 432 buffer.append( '"' ); 433 return buffer; 434 } 435 436 /** 437 * Add the <code>", PARAMETER["<name>", <value>]"</code> string 438 * to the specified string buffer. This is a convenience method 439 * for constructing WKT for "PARAM_MT". 440 */ 441 static void addParameter( final StringBuffer buffer, final String key, final double value ) { 442 buffer.append( ", PARAMETER[\"" ); 443 buffer.append( key ); 444 buffer.append( "\"," ); 445 buffer.append( value ); 446 buffer.append( ']' ); 447 } 448 449 /** 450 * Add the <code>", PARAMETER["<name>", <value>]"</code> string 451 * to the specified string buffer. This is a convenience method 452 * for constructing WKT for "PARAM_MT". 453 */ 454 static void addParameter( final StringBuffer buffer, final String key, final int value ) { 455 buffer.append( ", PARAMETER[\"" ); 456 buffer.append( key ); 457 buffer.append( "\"," ); 458 buffer.append( value ); 459 buffer.append( ']' ); 460 } 461 462 /** 463 * Invert the specified matrix in place. If the matrix can't be inverted 464 * because of a {@link SingularMatrixException}, then the exception is 465 * wrapped into a {@link NoninvertibleTransformException}. 466 */ 467 private static Matrix invert( final Matrix matrix ) 468 throws NoninvertibleTransformException { 469 try { 470 matrix.invert(); 471 return matrix; 472 } catch ( SingularMatrixException exception ) { 473 NoninvertibleTransformException e = new NoninvertibleTransformException( 474 Resources.format( ResourceKeys.ERROR_NONINVERTIBLE_TRANSFORM ) ); 475 throw e; 476 } 477 } 478 479 /** 480 * Default implementation for inverse math transform. 481 * This inner class is the inverse of the enclosing 482 * math transform. 483 * 484 * @version 1.0 485 * @author Martin Desruisseaux 486 */ 487 protected abstract class Inverse extends AbstractMathTransform { 488 /** 489 * Construct an inverse math transform. 490 */ 491 public Inverse() { 492 } 493 494 /** 495 * Gets the dimension of input points. The default 496 * implementation returns the dimension of output 497 * points of the enclosing math transform. 498 */ 499 public int getDimSource() { 500 return AbstractMathTransform.this.getDimTarget(); 501 } 502 503 /** 504 * Gets the dimension of output points. The default 505 * implementation returns the dimension of input 506 * points of the enclosing math transform. 507 */ 508 public int getDimTarget() { 509 return AbstractMathTransform.this.getDimSource(); 510 } 511 512 /** 513 * Gets the derivative of this transform at a point. The default 514 * implementation compute the inverse of the matrix returned by 515 * the enclosing math transform. 516 */ 517 public Matrix derivative( final Point2D point ) 518 throws TransformException { 519 return invert( AbstractMathTransform.this.derivative( point ) ); 520 } 521 522 /** 523 * Gets the derivative of this transform at a point. The default 524 * implementation compute the inverse of the matrix returned by 525 * the enclosing math transform. 526 */ 527 public Matrix derivative( final CoordinatePoint point ) 528 throws TransformException { 529 return invert( AbstractMathTransform.this.derivative( point ) ); 530 } 531 532 /** 533 * Returns the inverse of this math transform, which is the enclosing math transform. 534 * This method is declared final because some implementation assume that the inverse 535 * of <code>this</code> is always <code>AbstractMathTransform.this</code>. 536 */ 537 public final MathTransform inverse() { 538 return AbstractMathTransform.this; 539 } 540 541 /** 542 * Tests whether this transform does not move any points. 543 * The default implementation delegate this tests to the 544 * enclosing math transform. 545 */ 546 public boolean isIdentity() { 547 return AbstractMathTransform.this.isIdentity(); 548 } 549 550 /** 551 * Returns a hash code value for this math transform. 552 */ 553 public int hashCode() { 554 return ~AbstractMathTransform.this.hashCode(); 555 } 556 557 /** 558 * Compares the specified object with this inverse math 559 * transform for equality. The default implementation tests 560 * if <code>object</code> in an instance of the same class 561 * than <code>this</code>, and then test their enclosing 562 * math transforms. 563 */ 564 public boolean equals( final Object object ) { 565 if ( object == this ) 566 return true; // Slight optimization 567 if ( object instanceof Inverse ) { 568 final Inverse that = (Inverse) object; 569 return Utilities.equals( this.inverse(), that.inverse() ); 570 } 571 return false; 572 } 573 574 /** 575 * Returns the Well Know Text (WKT) 576 * for this inverse math transform. 577 */ 578 public String toString() { 579 return "INVERSE_MT[" + AbstractMathTransform.this + ']'; 580 } 581 } 582 }