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