    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/crs/transformations/Transformation.java $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005       Department of Geography, University of Bonn
    006     and
    007       lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035    ----------------------------------------------------------------------------*/
    036
    037    package org.deegree.crs.transformations;
    038
    039    import java.io.Serializable;
    040    import java.util.LinkedList;
    041    import java.util.List;
    042
    043    import javax.vecmath.Point3d;
    044
    045    import org.deegree.crs.Identifiable;
    046    import org.deegree.crs.coordinatesystems.CoordinateSystem;
    047    import org.deegree.crs.exceptions.TransformationException;
    048    import org.deegree.crs.transformations.coordinate.ConcatenatedTransform;
    049    import org.deegree.i18n.Messages;
    050
    051    /**
    052     * The <code>Transformation</code> class supplies the most basic method interface for any given transformation.
    053     *
    054     * The change of coordinates from one CRS to another CRS based on different datum is 'currently' only possible via a
    055     * coordinate <code>Transformation</code>.
    056     * <p>
    057     * The derivation of transformation parameters can be done empirically or analytically.
    058     * <p>
    059     * The quality (accuracy) of an empirical derivation strongly depends on the chosen reference points, there allocation,
    060     * and their number. Therefore different realizations for transformations from one datum to another exist. *
    061     * </p>
    062     * <p>
    063     * An analytic derivation is precise but mostly too complex to evaluate.
    064     * </p>
    065     *
    066     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
    067     *
    068     * @author last edited by: $Author: mschneider $
    069     *
    070     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
    071     *
    072     */
    073    public abstract class Transformation extends org.deegree.crs.Identifiable implements Serializable {
    074
    075        private static final long serialVersionUID = -8504028776871895959L;
    076
    077        private CoordinateSystem sourceCRS;
    078
    079        private CoordinateSystem targetCRS;
    080
    081        /**
    082         * Signaling this transformation as inverse
    083         */
    084        private boolean isInverse;
    085
    086        /**
    087         * @param sourceCRS
    088         * @param targetCRS
    089         * @param id
    090         *            an identifiable instance containing information about this transformation
    091         */
    092        public Transformation( CoordinateSystem sourceCRS, CoordinateSystem targetCRS, Identifiable id ) {
    093            super( id );
    094            checkForNullObject( targetCRS, "Transformation", "targetCRS" );
    095            // checkForNullObject( sourceCRS, "Transformation", "sourceCRS" );
    096            this.sourceCRS = sourceCRS;
    097            this.targetCRS = targetCRS;
    098            isInverse = false;
    099        }
    100
    101        /**
    102         * @return the name of the transformation.
    103         */
    104        public abstract String getImplementationName();
    105
    106        /**
    107         * Do a transformation, e.g. the incoming data will be transformed into other coordinates.
    108         *
    109         * @param srcPts
    110         *            the points which must be transformed, expected are following values either, long_1, lat_1, height_1,
    111         *            long_2, lat_2, height_2. or long_1, lat_1, long_2, lat_2
    112         * @return the transformed points
    113         * @throws TransformationException
    114         *             if a transform could not be calculated.
    115         */
    116        public abstract List<Point3d> doTransform( final List<Point3d> srcPts )
    117                                throws TransformationException;
    118
    119        /**
    120         * @return true if this transformation doesn't transform the incoming points. (e.g. is the id. matrix)
    121         */
    122        public abstract boolean isIdentity();
    123
    124        /**
    125         * Little helper function to create a temporary id or name.
    126         *
    127         * @param source
    128         *            containing the value (id or name) of the 'src' coourdinateSystem
    129         * @param dest
    130         *            containing the value (id or name) of the 'dest' coourdinateSystem
    131         * @return a following string "_SRC_fromValue_DEST_toValue".
    132         */
    133        public static String createFromTo( String source, String dest ) {
    134            return new StringBuilder( "_SRC_" ).append( source ).append( "_DEST_" ).append( dest ).toString();
    135        }
    136
    137        /**
    138         * Wraps the incoming coordinates into a List<Point3d> and calls the {@link #doTransform(List)}. The source array
    139         * will be read according to the dimension of the source CRS {@link #getSourceDimension()} and the target
    140         * coordinates will be put according to the dimension of the targetCRS {@link #getTargetDimension()}. If the
    141         * sourceDim &lt; 2 or &gt; 3 a transformation exception will be thrown.
    142         *
    143         * @param srcCoords
    144         *            the array holding the source ('original') coordinates.
    145         * @param startPositionSrc
    146         *            the position to start reading the coordinates from the source array (0 is the first).
    147         * @param destCoords
    148         *            the array which will receive the transformed coordinates.
    149         * @param startPositionDest
    150         *            the index of the destCoords array to put the results, if the result will exceed the array.length, the
    151         *            array will be enlarged to hold the transformed coordinates.
    152         * @param lastCoord
    153         *            the index of the last coordinate (normally length-1)
    154         * @throws TransformationException
    155         *             If the sourceDim &lt; 2 or soureDim &gt 3;
    156         * @throws IllegalArgumentException
    157         *             if
    158         *             <ul>
    159         *             <li>the srcCoords is null</li>
    160         *             <li>the startPositionSrc &gt; srcCoords.length</li>
    161         *             <li>the lastCoord &gt; startPositionSrc</li>
    162         *             <li>the number of source coordinates are not congruent with the source dimension</li>
    163         *             <li>the lastCoord &lt; startCoordSrc</li>
    164         *             <li>the source or target dimension &lt; 2 or &gt; 3</li>
    165         *             </ul>
    166         */
    167        public void doTransform( double[] srcCoords, int startPositionSrc, double[] destCoords, int startPositionDest,
    168                                 int lastCoord )
    169                                throws TransformationException {
    170            if ( startPositionSrc < 0 ) {
    171                startPositionSrc = 0;
    172            }
    173            if ( srcCoords == null ) {
    174                throw new IllegalArgumentException( Messages.getMessage( "CRS_PARAMETER_NOT_NULL",
    175                                                                         "doTransform(double[],int,double[],int,int)",
    176                                                                         "srcCoords" ) );
    177            }
    178            if ( startPositionSrc > srcCoords.length ) {
    179                throw new IllegalArgumentException( Messages.getMessage( "CRS_TRANSFORM_START_GT_LENGTH" ) );
    180            }
    181            if ( lastCoord > srcCoords.length ) {
    182                throw new IllegalArgumentException( Messages.getMessage( "CRS_TRANSFORM_END_GT_LENGTH" ) );
    183            }
    184            if ( ( lastCoord - startPositionSrc ) % getSourceDimension() != 0 ) {
    185                throw new IllegalArgumentException( Messages.getMessage( "CRS_TRANSFORM_SRC_WRONG_DIM" ) );
    186            }
    187            int listSize = ( lastCoord - startPositionSrc ) / getSourceDimension();
    188            if ( listSize < 0 ) {
    189                throw new IllegalArgumentException( Messages.getMessage( "CRS_TRANSFORM_LAST_LT_START" ) );
    190            }
    191
    192            List<Point3d> sourceCoords = new LinkedList<Point3d>();
    193            final int dim = getSourceDimension();
    194            if ( dim > 3 || dim < 2 ) {
    195                throw new TransformationException( Messages.getMessage( "CRS_TRANSFORM_WRONG_CRS_DIM", "source" ) );
    196            }
    197            for ( int i = startPositionSrc; i < lastCoord && ( i + ( dim - 1 ) ) < lastCoord; i += dim ) {
    198                sourceCoords.add( new Point3d( srcCoords[i], srcCoords[i + 1], ( dim == 3 ) ? srcCoords[i + 2] : 0 ) );
    199            }
    200            List<Point3d> result = doTransform( sourceCoords );
    201            if ( startPositionDest < 0 ) {
    202                startPositionDest = 0;
    203            }
    204            final int requiredSpace = result.size() * getTargetDimension();
    205            if ( destCoords == null ) {
    206                startPositionDest = 0;
    207                destCoords = new double[requiredSpace];
    208            }
    209            final int requiredSize = startPositionDest + requiredSpace;
    210            if ( requiredSize > destCoords.length ) {
    211                double[] tmp = new double[requiredSize];
    212                System.arraycopy( destCoords, 0, tmp, 0, startPositionDest );
    213                destCoords = tmp;
    214            }
    215            final int dimDest = getTargetDimension();
    216            if ( dimDest > 3 || dimDest < 2 ) {
    217                throw new TransformationException( Messages.getMessage( "CRS_TRANSFORM_WRONG_CRS_DIM", "target" ) );
    218            }
    219            int arrayPos = startPositionDest;
    220            for ( Point3d coord : result ) {
    221                destCoords[arrayPos++] = coord.x;
    222                destCoords[arrayPos++] = coord.y;
    223                if ( dimDest == 3 ) {
    224                    destCoords[arrayPos++] = coord.z;
    225                }
    226            }
    227
    228        }
    229
    230        /**
    231         * Transforms a single point3d (by calling the doTransform( List<Point3d>).
    232         *
    233         * @param coordinate
    234         *            to transform, if <code>null</code> null will be returned.
    235         * @return the transformed coordinate.
    236         * @throws TransformationException
    237         *             if the coordinate could not be transformed from the sourceCRS to the targetCRS.
    238         */
    239        public Point3d doTransform( Point3d coordinate )
    240                                throws TransformationException {
    241            if ( coordinate == null ) {
    242                return null;
    243            }
    244            List<Point3d> coord = new LinkedList<Point3d>();
    245            coord.add( coordinate );
    246            return doTransform( coord ).get( 0 );
    247        }
    248
    249        /**
    250         * @return true if the doInverseTransform method should be called, false otherwise.
    251         */
    252        public boolean isInverseTransform() {
    253            return isInverse;
    254        }
    255
    256        /**
    257         * This method flags the transformation about it's state. If this transformation was inverse calling this method
    258         * will result in a forward transformation and vice versa.
    259         */
    260        public void inverse() {
    261            isInverse = !isInverse;
    262        }
    263
    264        /**
    265         * @return a representation of this transformations name, including the 'Forward' or 'Inverse' modifier.
    266         */
    267        public String getTransformationName() {
    268            StringBuilder result = new StringBuilder( isInverse ? "Inverse " : "Forward " );
    269            result.append( getImplementationName() );
    270            return result.toString();
    271        }
    272
    273        /**
    274         * @return the sourceCRS.
    275         */
    276        public final CoordinateSystem getSourceCRS() {
    277            return isInverse ? targetCRS : sourceCRS;
    278        }
    279
    280        /**
    281         * @return the targetCRS.
    282         */
    283        public final CoordinateSystem getTargetCRS() {
    284            return isInverse ? sourceCRS : targetCRS;
    285        }
    286
    287        /**
    288         * @return the dimension of the source coordinateSystem.
    289         */
    290        public int getSourceDimension() {
    291            return getSourceCRS().getDimension();
    292        }
    293
    294        /**
    295         * @return the dimension of the target coordinateSystem.
    296         */
    297        public int getTargetDimension() {
    298            return getTargetCRS().getDimension();
    299        }
    300
    301        /**
    302         * Checks if this transformation is the inverse of the other transformation, which means, this.sourceCRS equals
    303         * other.targetCRS && this.targetCRS == other.sourceCRS. If Both transformations are identity this method also
    304         * returns true.
    305         *
    306         * @param other
    307         *            the transformation to check
    308         * @return true if this and the other transformation are each others inverse.
    309         */
    310        public boolean areInverse( Transformation other ) {
    311            return ( other == null ) ? false
    312                                    : ( this.isIdentity() && other.isIdentity() )
    313                                      || ( ( this.getSourceCRS().equals( other.getTargetCRS() ) && this.getTargetCRS().equals(
    314                                                                                                                               other.getSourceCRS() ) ) );
    315        }
    316
    317        /**
    318         * @param sb
    319         *            to add the transformation chain to, if <code>null</code> a new StringBuilder will be created.
    320         * @return the given StringBuilder (or a new instance) with the appended transformation steps.
    321         */
    322        public final StringBuilder getTransformationPath( StringBuilder sb ) {
    323            if ( sb == null ) {
    324                sb = new StringBuilder();
    325            }
    326            outputTransform( 0, sb, this );
    327            return sb;
    328        }
    329
    330        private int outputTransform( int level, StringBuilder sb, Transformation t ) {
    331            if ( t instanceof ConcatenatedTransform ) {
    332                level = outputTransform( level, sb, ( (ConcatenatedTransform) t ).getFirstTransform() );
    333                level = outputTransform( level, sb, ( (ConcatenatedTransform) t ).getSecondTransform() );
    334            } else {
    335                if ( level != 0 ) {
    336                    sb.append( "->" );
    337                }
    338                sb.append( "(" ).append( level ).append( ")" ).append( t.getTransformationName() );
    339                return ++level;
    340            }
    341            return level;
    342        }
    343
    344        /**
    345         * @param newSource
    346         *            to be used as the new source coordinate system.
    347         */
    348        public void setSourceCRS( CoordinateSystem newSource ) {
    349            this.sourceCRS = newSource;
    350        }
    351    }