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 }