001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }