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 }