001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 < 2 or > 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 < 2 or soureDim > 3;
156 * @throws IllegalArgumentException
157 * if
158 * <ul>
159 * <li>the srcCoords is null</li>
160 * <li>the startPositionSrc > srcCoords.length</li>
161 * <li>the lastCoord > startPositionSrc</li>
162 * <li>the number of source coordinates are not congruent with the source dimension</li>
163 * <li>the lastCoord < startCoordSrc</li>
164 * <li>the source or target dimension < 2 or > 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 }