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 < 2 or > 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 < 2 or soureDim > 3; 190 * @throws IllegalArgumentException 191 * if 192 * <ul> 193 * <li> the srcCoords is null</li> 194 * <li>the startPositionSrc > srcCoords.length</li> 195 * <li> the lastCoord > startPositionSrc</li> 196 * <li>the number of source coords are not kongruent with the source dimension</li> 197 * <li> the lastCoord < startCoordSrc</li> 198 * <li>the source or target dimension < 2 or > 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 }