/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/exse/ lat/lon GmbH http://www.lat-lon.de It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification (C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/) SEAGIS Contacts: Surveillance de l'Environnement Assist�e par Satellite Institut de Recherche pour le D�veloppement / US-Espace mailto:seasnet@teledetection.fr This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53115 Bonn Germany E-Mail: poth@lat-lon.de Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: klaus.greve@uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.model.csct.ct; // OpenGIS dependencies import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.IllegalPathStateException; import java.awt.geom.Line2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.QuadCurve2D; import java.util.Locale; import javax.vecmath.SingularMatrixException; import org.deegree.model.csct.pt.CoordinatePoint; import org.deegree.model.csct.pt.Matrix; import org.deegree.model.csct.pt.MismatchedDimensionException; import org.deegree.model.csct.resources.Geometry; import org.deegree.model.csct.resources.Utilities; import org.deegree.model.csct.resources.css.ResourceKeys; import org.deegree.model.csct.resources.css.Resources; /** * Provides a default implementations for most methods required by the * {@link MathTransform} interface. <code>AbstractMathTransform</code> * provides a convenient base class from which other transform classes * can be easily derived. In addition, <code>AbstractMathTransform</code> * implements methods required by the {@link MathTransform2D} interface, * but <strong>does not</strong> implements <code>MathTransform2D</code>. * Subclasses must declare <code>implements MathTransform2D</code> * themself if they know to maps two-dimensional coordinate systems. * * @version 1.0 * @author Martin Desruisseaux */ public abstract class AbstractMathTransform implements MathTransform { /** * Construct a math transform. */ public AbstractMathTransform() { } /** * Returns a human readable name, if available. If no name is available in * the specified locale, then this method returns a name in an arbitrary * locale. If no name is available in any locale, then this method returns * <code>null</code>. The default implementation always returns <code>null</code>. * * @param locale The desired locale, or <code>null</code> for a default locale. * @return The transform name localized in the specified locale if possible, or * <code>null</code> if no name is available in any locale. */ protected String getName( final Locale locale ) { return null; } /** * Transforms the specified <code>ptSrc</code> and stores the result in <code>ptDst</code>. * The default implementation invokes {@link #transform(double[],int,double[],int,int)} * using a temporary array of doubles. * * @param ptSrc the specified coordinate point to be transformed. * @param ptDst the specified coordinate point that stores the * result of transforming <code>ptSrc</code>, or * <code>null</code>. * @return the coordinate point after transforming <code>ptSrc</code> * and stroring the result in <code>ptDst</code>. * @throws MismatchedDimensionException if this transform * doesn't map two-dimensional coordinate systems. * @throws TransformException if the point can't be transformed. * * @see MathTransform2D#transform(Point2D,Point2D) */ public Point2D transform( final Point2D ptSrc, final Point2D ptDst ) throws TransformException { if ( getDimSource() != 2 || getDimTarget() != 2 ) { throw new MismatchedDimensionException(); } final double[] ord = new double[] { ptSrc.getX(), ptSrc.getY() }; this.transform( ord, 0, ord, 0, 1 ); if ( ptDst != null ) { ptDst.setLocation( ord[0], ord[1] ); return ptDst; } return new Point2D.Double( ord[0], ord[1] ); } /** * Transforms the specified <code>ptSrc</code> and stores the result * in <code>ptDst</code>. The default implementation invokes * {@link #transform(double[],int,double[],int,int)}. */ public CoordinatePoint transform( final CoordinatePoint ptSrc, CoordinatePoint ptDst ) throws TransformException { final int pointDim = ptSrc.getDimension(); final int sourceDim = getDimSource(); final int targetDim = getDimTarget(); if ( pointDim != sourceDim ) { throw new MismatchedDimensionException( pointDim, sourceDim ); } if ( ptDst == null ) { ptDst = new CoordinatePoint( targetDim ); } else if ( ptDst.getDimension() != targetDim ) { throw new MismatchedDimensionException( ptDst.getDimension(), targetDim ); } transform( ptSrc.ord, 0, ptDst.ord, 0, 1 ); return ptDst; } /** * Transforms a list of coordinate point ordinal values. The default implementation * invokes {@link #transform(double[],int,double[],int,int)} using a temporary array * of doubles. */ public void transform( final float[] srcPts, final int srcOff, final float[] dstPts, final int dstOff, final int numPts ) throws TransformException { final int dimSource = getDimSource(); final int dimTarget = getDimTarget(); final double[] tmpPts = new double[numPts * Math.max( dimSource, dimTarget )]; for ( int i = numPts * dimSource; --i >= 0; ) tmpPts[i] = srcPts[srcOff + i]; transform( tmpPts, 0, tmpPts, 0, numPts ); for ( int i = numPts * dimTarget; --i >= 0; ) dstPts[dstOff + i] = (float) tmpPts[i]; } /** * Transform the specified shape. The default implementation compute * quadratic curves using three points for each shape's segments. * * @param shape Shape to transform. * @return Transformed shape, or <code>shape</code> if * this transform is the identity transform. * @throws IllegalStateException if this transform doesn't map 2D coordinate systems. * @throws TransformException if a transform failed. * * @see MathTransform2D#createTransformedShape(Shape) */ public Shape createTransformedShape( final Shape shape ) throws TransformException { return isIdentity() ? shape : createTransformedShape( shape, null, null, Geometry.PARALLEL ); } /** * Transforme une forme g�om�trique. Cette m�thode copie toujours les coordonn�es * transform�es dans un nouvel objet. La plupart du temps, elle produira un objet * {@link GeneralPath}. Elle peut aussi retourner des objets {@link Line2D} ou * {@link QuadCurve2D} si une telle simplification est possible. * * @param shape Forme g�om�trique � transformer. * @param preTr Transformation affine � appliquer <em>avant</em> de transformer la forme * <code>shape</code>, ou <code>null</code> pour ne pas en appliquer. * Cet argument sera surtout utile lors des transformations inverses. * @param postTr Transformation affine � appliquer <em>apr�s</em> avoir transform�e la * forme <code>shape</code>, ou <code>null</code> pour ne pas en appliquer. * Cet argument sera surtout utile lors des transformations directes. * @param quadDir Direction des courbes quadratiques ({@link Geometry#HORIZONTAL} * ou {@link Geometry#PARALLEL}). * * @return La forme g�om�trique transform�e. * @throws MismatchedDimensionException if this transform * doesn't map two-dimensional coordinate systems. * @throws TransformException Si une transformation a �chou�. */ final Shape createTransformedShape( final Shape shape, final AffineTransform preTr, final AffineTransform postTr, final int quadDir ) throws TransformException { if ( getDimSource() != 2 || getDimTarget() != 2 ) { throw new MismatchedDimensionException(); } final PathIterator it = shape.getPathIterator( preTr ); final GeneralPath path = new GeneralPath( it.getWindingRule() ); final Point2D.Float ctrl = new Point2D.Float(); final double[] buffer = new double[6]; double ax = 0, ay = 0; // Coordonn�es du dernier point avant la projection. double px = 0, py = 0; // Coordonn�es du dernier point apr�s la projection. int indexCtrlPt = 0; // Index du point de contr�le dans 'buffer'. int indexLastPt = 0; // Index du dernier point dans 'buffer'. for ( ; !it.isDone(); it.next() ) { switch ( it.currentSegment( buffer ) ) { default: { throw new IllegalPathStateException(); } case PathIterator.SEG_CLOSE: { /* * Ferme la forme g�om�trique, puis continue la boucle. On utilise une * instruction 'continue' plut�t que 'break' car il ne faut pas ex�cuter * le code qui suit ce 'switch'. */ path.closePath(); continue; } case PathIterator.SEG_MOVETO: { /* * M�morise les coordonn�es sp�cifi�es (avant et apr�s les avoir * projet�es), puis continue la boucle. On utilise une instruction * 'continue' plut�t que 'break' car il ne faut pas ex�cuter le * code qui suit ce 'switch'. */ ax = buffer[0]; ay = buffer[1]; transform( buffer, 0, buffer, 0, 1 ); path.moveTo( (float) ( px = buffer[0] ), (float) ( py = buffer[1] ) ); continue; } case PathIterator.SEG_LINETO: { /* * Place dans 'buffer[2,3]' les coordonn�es * d'un point qui se trouve sur la droite: * * x = 0.5*(x1+x2) * y = 0.5*(y1+y2) * * Ce point sera trait� apr�s le 'switch', d'o� * l'utilisation d'un 'break' plut�t que 'continue'. */ indexLastPt = 0; indexCtrlPt = 2; buffer[2] = 0.5 * ( ax + ( ax = buffer[0] ) ); buffer[3] = 0.5 * ( ay + ( ay = buffer[1] ) ); break; } case PathIterator.SEG_QUADTO: { /* * Place dans 'buffer[0,1]' les coordonn�es * d'un point qui se trouve sur la courbe: * * x = 0.5*(ctrlx + 0.5*(x1+x2)) * y = 0.5*(ctrly + 0.5*(y1+y2)) * * Ce point sera trait� apr�s le 'switch', d'o� * l'utilisation d'un 'break' plut�t que 'continue'. */ indexLastPt = 2; indexCtrlPt = 0; buffer[0] = 0.5 * ( buffer[0] + 0.5 * ( ax + ( ax = buffer[2] ) ) ); buffer[1] = 0.5 * ( buffer[1] + 0.5 * ( ay + ( ay = buffer[3] ) ) ); break; } case PathIterator.SEG_CUBICTO: { /* * Place dans 'buffer[0,1]' les coordonn�es * d'un point qui se trouve sur la courbe: * * x = 0.25*(1.5*(ctrlx1+ctrlx2) + 0.5*(x1+x2)); * y = 0.25*(1.5*(ctrly1+ctrly2) + 0.5*(y1+y2)); * * Ce point sera trait� apr�s le 'switch', d'o� * l'utilisation d'un 'break' plut�t que 'continue'. * * NOTE: Le point calcul� est bien sur la courbe, mais n'est pas n�cessairement repr�sentatif. * Cet algorithme remplace les deux points de contr�les par un seul, ce qui se traduit * par une perte de souplesse qui peut donner de mauvais r�sultats si la courbe cubique * �tait bien tordue. Projeter une courbe cubique ne me semble pas �tre un probl�me simple, * mais heureusement ce cas devrait �tre assez rare. Il se produira le plus souvent si on * essaye de projeter un cercle ou une ellipse, auxquels cas l'algorithme actuel donnera * quand m�me des r�sultats tol�rables. */ indexLastPt = 4; indexCtrlPt = 0; buffer[0] = 0.25 * ( 1.5 * ( buffer[0] + buffer[2] ) + 0.5 * ( ax + ( ax = buffer[4] ) ) ); buffer[1] = 0.25 * ( 1.5 * ( buffer[1] + buffer[3] ) + 0.5 * ( ay + ( ay = buffer[5] ) ) ); break; } } /* * Applique la transformation sur les points qui se * trouve dans le buffer, puis ajoute ces points � * la forme g�om�trique projet�e comme une courbe * quadratique. */ transform( buffer, 0, buffer, 0, 2 ); if ( Geometry.parabolicControlPoint( px, py, buffer[indexCtrlPt], buffer[indexCtrlPt + 1], buffer[indexLastPt], buffer[indexLastPt + 1], quadDir, ctrl ) != null ) { path.quadTo( ctrl.x, ctrl.y, (float) ( px = buffer[indexLastPt + 0] ), (float) ( py = buffer[indexLastPt + 1] ) ); } else path.lineTo( (float) ( px = buffer[indexLastPt + 0] ), (float) ( py = buffer[indexLastPt + 1] ) ); } /* * La projection de la forme g�om�trique est termin�e. Applique * une transformation affine si c'�tait demand�e, puis retourne * une version si possible simplifi�e de la forme g�om�trique. */ if ( postTr != null ) { path.transform( postTr ); } return Geometry.toPrimitive( path ); } /** * Gets the derivative of this transform at a point. The default * implementation invokes {@link #derivative(CoordinatePoint)}. * * @param point The coordinate point where to evaluate the derivative. * @return The derivative at the specified point as a 2x2 matrix. * @throws MismatchedDimensionException if the input dimension is not 2. * @throws TransformException if the derivative can't be evaluated at the specified point. * * @see MathTransform2D#derivative(Point2D) */ public Matrix derivative( final Point2D point ) throws TransformException { return derivative( new CoordinatePoint( point ) ); } /** * Gets the derivative of this transform at a point. The default * implementation throws an {@link UnsupportedOperationException} * <strong>(note: this default implementation may change in a future * version)</strong>. * * @param point The coordinate point where to evaluate the derivative. * @return The derivative at the specified point (never <code>null</code>). * @throws TransformException if the derivative can't be evaluated at the specified point. */ public Matrix derivative( final CoordinatePoint point ) throws TransformException { throw new UnsupportedOperationException( "Matrix derivative not yet implemented" ); } /** * Creates the inverse transform of this object. * The default implementation returns <code>this</code> if this transform is an identity * transform, and throws a {@link NoninvertibleTransformException} otherwise. Subclasses * should override this method. */ public MathTransform inverse() throws NoninvertibleTransformException { if ( isIdentity() ) return this; throw new NoninvertibleTransformException( Resources.format( ResourceKeys.ERROR_NONINVERTIBLE_TRANSFORM ) ); } /** * Returns a hash value for this transform. */ public int hashCode() { return getDimSource() + 37 * getDimTarget(); } /** * Compares the specified object with this math transform for equality. * The default implementation checks if <code>object</code> is an instance * of the same class than <code>this</code>. Subclasses should override * this method in order to compare internal fields. */ public boolean equals( final Object object ) { // Do not check 'object==this' here, since this // optimization is usually done in subclasses. return ( object != null && getClass().equals( object.getClass() ) ); } /** * Returns a string repr�sentation of this transform. * Subclasses should override this method in order to * returns Well Know Text (WKT) instead. */ public String toString() { final StringBuffer buffer = new StringBuffer( Utilities.getShortClassName( this ) ); buffer.append( '[' ); buffer.append( getDimSource() ); buffer.append( "D \u2192 " ); // Arrow --> buffer.append( getDimTarget() ); buffer.append( "D]" ); return buffer.toString(); } /** * Returns a string buffer initialized with "PARAM_MT" * and a classification name. This is a convenience * method for WKT formatting. */ static StringBuffer paramMT( final String classification ) { final StringBuffer buffer = new StringBuffer( "PARAM_MT[\"" ); buffer.append( classification ); buffer.append( '"' ); return buffer; } /** * Add the <code>", PARAMETER["<name>", <value>]"</code> string * to the specified string buffer. This is a convenience method * for constructing WKT for "PARAM_MT". */ static void addParameter( final StringBuffer buffer, final String key, final double value ) { buffer.append( ", PARAMETER[\"" ); buffer.append( key ); buffer.append( "\"," ); buffer.append( value ); buffer.append( ']' ); } /** * Add the <code>", PARAMETER["<name>", <value>]"</code> string * to the specified string buffer. This is a convenience method * for constructing WKT for "PARAM_MT". */ static void addParameter( final StringBuffer buffer, final String key, final int value ) { buffer.append( ", PARAMETER[\"" ); buffer.append( key ); buffer.append( "\"," ); buffer.append( value ); buffer.append( ']' ); } /** * Invert the specified matrix in place. If the matrix can't be inverted * because of a {@link SingularMatrixException}, then the exception is * wrapped into a {@link NoninvertibleTransformException}. */ private static Matrix invert( final Matrix matrix ) throws NoninvertibleTransformException { try { matrix.invert(); return matrix; } catch ( SingularMatrixException exception ) { NoninvertibleTransformException e = new NoninvertibleTransformException( Resources.format( ResourceKeys.ERROR_NONINVERTIBLE_TRANSFORM ) ); throw e; } } /** * Default implementation for inverse math transform. * This inner class is the inverse of the enclosing * math transform. * * @version 1.0 * @author Martin Desruisseaux */ protected abstract class Inverse extends AbstractMathTransform { /** * Construct an inverse math transform. */ public Inverse() { } /** * Gets the dimension of input points. The default * implementation returns the dimension of output * points of the enclosing math transform. */ public int getDimSource() { return AbstractMathTransform.this.getDimTarget(); } /** * Gets the dimension of output points. The default * implementation returns the dimension of input * points of the enclosing math transform. */ public int getDimTarget() { return AbstractMathTransform.this.getDimSource(); } /** * Gets the derivative of this transform at a point. The default * implementation compute the inverse of the matrix returned by * the enclosing math transform. */ public Matrix derivative( final Point2D point ) throws TransformException { return invert( AbstractMathTransform.this.derivative( point ) ); } /** * Gets the derivative of this transform at a point. The default * implementation compute the inverse of the matrix returned by * the enclosing math transform. */ public Matrix derivative( final CoordinatePoint point ) throws TransformException { return invert( AbstractMathTransform.this.derivative( point ) ); } /** * Returns the inverse of this math transform, which is the enclosing math transform. * This method is declared final because some implementation assume that the inverse * of <code>this</code> is always <code>AbstractMathTransform.this</code>. */ public final MathTransform inverse() { return AbstractMathTransform.this; } /** * Tests whether this transform does not move any points. * The default implementation delegate this tests to the * enclosing math transform. */ public boolean isIdentity() { return AbstractMathTransform.this.isIdentity(); } /** * Returns a hash code value for this math transform. */ public int hashCode() { return ~AbstractMathTransform.this.hashCode(); } /** * Compares the specified object with this inverse math * transform for equality. The default implementation tests * if <code>object</code> in an instance of the same class * than <code>this</code>, and then test their enclosing * math transforms. */ public boolean equals( final Object object ) { if ( object == this ) return true; // Slight optimization if ( object instanceof Inverse ) { final Inverse that = (Inverse) object; return Utilities.equals( this.inverse(), that.inverse() ); } return false; } /** * Returns the Well Know Text (WKT) * for this inverse math transform. */ public String toString() { return "INVERSE_MT[" + AbstractMathTransform.this + ']'; } } }