001    //$HeadURL: https://sushibar/svn/deegree/base/trunk/src/org/deegree/model/csct/ct/CoordinateTransformationFactory.java $
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    package org.deegree.crs.transformations;
039    
040    // OpenGIS dependencies
041    import javax.vecmath.GMatrix;
042    import javax.vecmath.Matrix3d;
043    import javax.vecmath.Matrix4d;
044    
045    import org.deegree.crs.components.Axis;
046    import org.deegree.crs.components.Ellipsoid;
047    import org.deegree.crs.components.GeodeticDatum;
048    import org.deegree.crs.components.PrimeMeridian;
049    import org.deegree.crs.components.Unit;
050    import org.deegree.crs.coordinatesystems.CoordinateSystem;
051    import org.deegree.crs.coordinatesystems.GeocentricCRS;
052    import org.deegree.crs.coordinatesystems.GeographicCRS;
053    import org.deegree.crs.coordinatesystems.ProjectedCRS;
054    import org.deegree.crs.exceptions.TransformationException;
055    import org.deegree.crs.projections.ProjectionUtils;
056    import org.deegree.crs.utilities.Matrix;
057    import org.deegree.framework.log.ILogger;
058    import org.deegree.framework.log.LoggerFactory;
059    
060    /**
061     * The <code>GeocentricTransform</code> class is the central access point for all transformations between different
062     * crs's.
063     * <p>
064     * It creates a transformation chain for two given CoordinateSystems by considering their type. For example the
065     * Transformation chain from EPSG:31466 ( a projected crs with underlying geographic crs epsg:4314 using the DHDN datum
066     * and the TransverseMercator Projection) to EPSG:28992 (another projected crs with underlying geographic crs epsg:4289
067     * using the 'new Amersfoort Datum' and the StereographicAzimuthal Projection) would result in following Transformation
068     * Chain:
069     * <ol>
070     * <li>Inverse projection - thus getting the coordinates in lat/lon for geographic crs epsg:4314</li>
071     * <li>Geodetic transformation - thus getting x-y-z coordinates for geographic crs epsg:4314</li>
072     * <li>WGS84 transformation -thus getting the x-y-z coordinates for the WGS84 datum </li>
073     * <li>Inverse WGS84 transformation -thus getting the x-y-z coordinates for the geodetic from epsg:4289</li>
074     * <li>Inverse geodetic - thus getting the lat/lon for epsg:4289</li>
075     * <li>projection - getting the coordinates (in meters) for epsg:28992</li>
076     * </ol>
077     * 
078     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
079     * 
080     * @author last edited by: $Author:$
081     * 
082     * @version $Revision:$, $Date:$
083     * 
084     */
085    public class TransformationFactory {
086        private static ILogger LOG = LoggerFactory.getLogger( TransformationFactory.class );
087    
088        /**
089         * The default coordinate transformation factory. Will be constructed only when first needed.
090         */
091        private static TransformationFactory DEFAULT_INSTANCE = null;
092    
093        private TransformationFactory() {
094            // nottin
095        }
096    
097        /**
098         * @return the default coordinate transformation factory.
099         */
100        public static synchronized TransformationFactory getInstance() {
101            if ( DEFAULT_INSTANCE == null ) {
102                // DEFAULT_INSTANCE = new CoordinateTransformationFactory( MathTransformFactory.getDefault() );
103                DEFAULT_INSTANCE = new TransformationFactory();
104            }
105            return DEFAULT_INSTANCE;
106        }
107    
108        /**
109         * Creates a transformation between two coordinate systems. This method will examine the coordinate systems in order
110         * to construct a transformation between them. This method may fail if no path between the coordinate systems is
111         * found.
112         * 
113         * @param sourceCRS
114         *            Input coordinate system.
115         * @param targetCRS
116         *            Output coordinate system.
117         * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>.
118         * @throws TransformationException
119         * @throws TransformationException
120         *             if no transformation path has been found.
121         * 
122         */
123        public CRSTransformation createFromCoordinateSystems( final CoordinateSystem sourceCRS,
124                                                              final CoordinateSystem targetCRS )
125                                                                                                throws TransformationException {
126            if ( sourceCRS == null ) {
127                throw new IllegalArgumentException( "The source CRS may not be null" );
128            }
129            if ( targetCRS == null ) {
130                throw new IllegalArgumentException( "The target CRS may not be null" );
131            }
132            if ( sourceCRS.equals( targetCRS ) ) {
133                LOG.logDebug( "Source crs and target crs are equal, no transformation needed (returning identity matrix)." );
134                final Matrix matrix = new Matrix( sourceCRS.getDimension() + 1 );
135                matrix.setIdentity();
136                return createAffineTransform( sourceCRS, targetCRS, matrix );
137            }
138            if ( ( sourceCRS.getType() != CoordinateSystem.GEOGRAPHIC_CRS && sourceCRS.getType() != CoordinateSystem.PROJECTED_CRS && sourceCRS.getType() != CoordinateSystem.GEOCENTRIC_CRS ) || ( targetCRS.getType() != CoordinateSystem.GEOGRAPHIC_CRS && targetCRS.getType() != CoordinateSystem.PROJECTED_CRS && targetCRS.getType() != CoordinateSystem.GEOCENTRIC_CRS ) ) {
139                throw new TransformationException( sourceCRS,
140                                                   targetCRS,
141                                                   "Either the target crs type or the source crs type was unknown" );
142            }
143            CRSTransformation result = null;
144            if ( sourceCRS.getType() == CoordinateSystem.GEOGRAPHIC_CRS ) {
145                /**
146                 * Geographic --> Geographic, Projected or Geocentric
147                 */
148                final GeographicCRS source = (GeographicCRS) sourceCRS;
149                if ( targetCRS.getType() == CoordinateSystem.GEOGRAPHIC_CRS ) {
150                    result = createTransformationStep( source, (GeographicCRS) targetCRS );
151                }
152                if ( targetCRS.getType() == CoordinateSystem.PROJECTED_CRS ) {
153                    result = createTransformationStep( source, (ProjectedCRS) targetCRS );
154                }
155                if ( targetCRS.getType() == CoordinateSystem.GEOCENTRIC_CRS ) {
156                    result = createTransformationStep( source, (GeocentricCRS) targetCRS );
157                }
158            } else if ( sourceCRS.getType() == CoordinateSystem.PROJECTED_CRS ) {
159                /**
160                 * Projected --> Projected, Geographic or Geocentric
161                 */
162                final ProjectedCRS source = (ProjectedCRS) sourceCRS;
163                if ( targetCRS.getType() == CoordinateSystem.PROJECTED_CRS ) {
164                    result = createTransformationStep( source, (ProjectedCRS) targetCRS );
165                }
166                if ( targetCRS.getType() == CoordinateSystem.GEOGRAPHIC_CRS ) {
167                    result = createTransformationStep( source, (GeographicCRS) targetCRS );
168                }
169                if ( targetCRS.getType() == CoordinateSystem.GEOCENTRIC_CRS ) {
170                    result = createTransformationStep( source, (GeocentricCRS) targetCRS );
171                }
172            } else if ( sourceCRS.getType() == CoordinateSystem.GEOCENTRIC_CRS ) {
173                /**
174                 * Geocentric --> Geocentric, Horizontal or Compound
175                 */
176                final GeocentricCRS source = (GeocentricCRS) sourceCRS;
177                if ( targetCRS.getType() == CoordinateSystem.GEOCENTRIC_CRS ) {
178                    result = createTransformationStep( source, (GeocentricCRS) targetCRS );
179                }
180            }
181            if ( result == null ) {
182                LOG.logDebug( "The resulting transformation was null, returning an identity matrix." );
183                final Matrix matrix = new Matrix( sourceCRS.getDimension() + 1 );
184                matrix.setIdentity();
185                result = createAffineTransform( sourceCRS, targetCRS, matrix );
186            }
187            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
188                StringBuilder output = new StringBuilder( "The resulting transformation chain: \n" );
189                output = result.getTransformationPath( output );
190                LOG.logDebug( output.toString() );
191            }
192            return result;
193    
194        }
195    
196        /**
197         * Creates a transformation between two geographic coordinate systems. This method is automatically invoked by
198         * {@link #createFromCoordinateSystems createFromCoordinateSystems(...)}. The default implementation can adjust
199         * axis order and orientation (e.g. transforming from <code>(NORTH,WEST)</code> to <code>(EAST,NORTH)</code>),
200         * performs units conversion and apply Bursa Wolf transformation if needed.
201         * 
202         * @param sourceCS
203         *            Input coordinate system.
204         * @param targetCS
205         *            Output coordinate system.
206         * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>.
207         * @throws TransformationException
208         *             if no transformation path has been found.
209         */
210        private CRSTransformation createTransformationStep( final GeographicCRS sourceCS, final GeographicCRS targetCS )
211                                                                                                                        throws TransformationException {
212            if ( sourceCS.getGeodeticDatum().equals( targetCS.getGeodeticDatum() ) ) {
213                LOG.logDebug( "The datums of geographic (source): " + sourceCS.getIdAndName()
214                              + " equals geographic (target): "
215                              + targetCS.getIdAndName()
216                              + " returning null" );
217                return null;
218            }
219            LOG.logDebug( "Creating geographic ->geographic transformation: from (source): " + sourceCS.getIdAndName()
220                          + " to(target): "
221                          + targetCS.getIdAndName() );
222            final GeodeticDatum sourceDatum = sourceCS.getGeodeticDatum();
223            final GeodeticDatum targetDatum = targetCS.getGeodeticDatum();
224            final Ellipsoid sourceEllipsoid = sourceDatum.getEllipsoid();
225            final Ellipsoid targetEllipsoid = targetDatum.getEllipsoid();
226            if ( sourceEllipsoid != null && !sourceEllipsoid.equals( targetEllipsoid ) ) {
227                /*
228                 * If the two geographic coordinate systems use differents ellipsoid, convert from the source to target
229                 * ellipsoid through the geocentric coordinate system.
230                 */
231                final GeocentricCRS sourceGCS = new GeocentricCRS( sourceDatum,
232                                                                   sourceCS.getIdentifier(),
233                                                                   sourceCS.getName() + "Geocentric" );
234                final GeocentricCRS targetGCS = new GeocentricCRS( targetDatum,
235                                                                   targetCS.getIdentifier(),
236                                                                   targetCS.getName() + "Geocentric" );
237                CRSTransformation step1 = createTransformationStep( sourceCS, sourceGCS );
238                LOG.logDebug( "Step 1 from geographic to geographic:\n" + step1 );
239                CRSTransformation step2 = createTransformationStep( sourceGCS, targetGCS );
240                LOG.logDebug( "Step 2 from geographic to geographic:\n" + step2 );
241                CRSTransformation step3 = createTransformationStep( targetCS, targetGCS );
242                LOG.logDebug( "Step 3 from geographic to geographic:\n" + step3 );
243                if ( step3 != null ) {
244                    step3.inverse();// call inversetransform from step 3.
245                }
246                return concatenate( step1, step2, step3 );
247            }
248    
249            /*
250             * Swap axis order, and rotate the longitude coordinate if prime meridians are different.
251             */
252            final Matrix matrix = swapAndScaleGeoAxis( sourceCS, targetCS );
253            // TODO: We should ensure that longitude is in range [-180..+180°].
254            return createAffineTransform( sourceCS, targetCS, matrix );
255            // return createFromMathTransform( sourceCS, targetCS, TransformType.CONVERSION, transform );
256        }
257    
258        /**
259         * Creates a transformation between a geographic and a projected coordinate systems. This method is automatically
260         * invoked by {@link #createFromCoordinateSystems createFromCoordinateSystems(...)}.
261         * 
262         * @param sourceCRS
263         *            Input coordinate system.
264         * @param targetCRS
265         *            Output coordinate system.
266         * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>.
267         * @throws TransformationException
268         *             if no transformation path has been found.
269         */
270        private CRSTransformation createTransformationStep( final GeographicCRS sourceCRS, final ProjectedCRS targetCRS )
271                                                                                                                         throws TransformationException {
272    
273            LOG.logDebug( "Creating geographic->projected transformation: from (source): " + sourceCRS.getIdAndName()
274                          + " to(target): "
275                          + targetCRS.getIdAndName() );
276            final GeographicCRS stepGeoCS = targetCRS.getGeographicCRS();
277    
278            // should perform a datum shift
279            final CRSTransformation step1 = createTransformationStep( sourceCRS, stepGeoCS );
280            final CRSTransformation step2 = new ProjectionTransform( targetCRS );
281            return concatenate( step1, step2 );
282        }
283    
284        /**
285         * Creates a transformation between a geographic and a geocentric coordinate systems. Since the source coordinate
286         * systems doesn't have a vertical axis, height above the ellipsoid is assumed equals to zero everywhere. This
287         * method is automatically invoked by {@link #createFromCoordinateSystems createFromCoordinateSystems(...)}.
288         * 
289         * @param sourceCS
290         *            Input geographic coordinate system.
291         * @param targetCS
292         *            Output coordinate system.
293         * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>.
294         * @throws TransformationException
295         *             if no transformation path has been found.
296         */
297        private CRSTransformation createTransformationStep( final GeographicCRS sourceCS, final GeocentricCRS targetCS )
298                                                                                                                        throws TransformationException {
299            LOG.logDebug( "Creating geographic ->geocentric transformation: from (source): " + sourceCS.getIdAndName()
300                          + " to(target): "
301                          + targetCS.getIdAndName() );
302            if ( !PrimeMeridian.GREENWICH.equals( targetCS.getGeodeticDatum().getPrimeMeridian() ) ) {
303                throw new TransformationException( "Rotation of prime meridian not yet implemented" );
304            }
305            final CRSTransformation step1 = createAffineTransform( sourceCS,
306                                                                   targetCS,
307                                                                   swapAndScaleGeoAxis( sourceCS, GeographicCRS.WGS84 ) );
308    
309            // TODO maybe something for a pool of transformations?
310            final CRSTransformation step2 = new GeocentricTransform( sourceCS, targetCS );
311            return createConcatenatedTransform( step1, step2 );
312        }
313    
314        /**
315         * Creates a transformation between two projected coordinate systems. This method is automatically invoked by
316         * {@link #createFromCoordinateSystems createFromCoordinateSystems(...)}. The default implementation can adjust
317         * axis order and orientation. It also performs units conversion if it is the only extra change needed. Otherwise,
318         * it performs three steps:
319         * 
320         * <ol>
321         * <li>Unproject <code>sourceCS</code>.</li>
322         * <li>Transform from <code>sourceCS.geographicCS</code> to <code>targetCS.geographicCS</code>.</li>
323         * <li>Project <code>targetCS</code>.</li>
324         * </ol>
325         * 
326         * @param sourceCS
327         *            Input coordinate system.
328         * @param targetCS
329         *            Output coordinate system.
330         * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>.
331         * @throws TransformationException
332         *             if no transformation path has been found.
333         */
334        private CRSTransformation createTransformationStep( final ProjectedCRS sourceCS, final ProjectedCRS targetCS )
335                                                                                                                      throws TransformationException {
336            LOG.logDebug( "Creating projected -> projected transformation: from (source): " + sourceCS.getIdAndName()
337                          + " to(target): "
338                          + targetCS.getIdAndName() );
339            if ( sourceCS.getProjection().equals( targetCS.getProjection() ) ) {
340                return null;
341            }
342            final GeographicCRS sourceGeo = sourceCS.getGeographicCRS();
343            final GeographicCRS targetGeo = targetCS.getGeographicCRS();
344            final CRSTransformation step1 = createTransformationStep( sourceCS, sourceGeo );
345            LOG.logDebug( "Step 1: " + step1 );
346            final CRSTransformation step2 = createTransformationStep( sourceGeo, targetGeo );
347            LOG.logDebug( "Step 2: " + step2 );
348            final CRSTransformation step3 = createTransformationStep( targetGeo, targetCS );
349            LOG.logDebug( "Step 3: " + step3 );
350            return concatenate( step1, step2, step3 );
351        }
352    
353        /**
354         * Creates a transformation between a projected and a geocentric coordinate systems. This method is automatically
355         * invoked by {@link #createFromCoordinateSystems createFromCoordinateSystems(...)}. This method doesn't need to be
356         * public since its decomposition in two step should be general enough.
357         * 
358         * @param sourceCS
359         *            Input projected coordinate system.
360         * @param targetCS
361         *            Output coordinate system.
362         * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>.
363         * @throws TransformationException
364         *             if no transformation path has been found.
365         */
366        private CRSTransformation createTransformationStep( final ProjectedCRS sourceCS, final GeocentricCRS targetCS )
367                                                                                                                       throws TransformationException {
368            LOG.logDebug( "Creating projected -> geocentric transformation: from (source): " + sourceCS.getIdAndName()
369                          + " to(target): "
370                          + targetCS.getIdAndName() );
371            final GeographicCRS sourceGCS = sourceCS.getGeographicCRS();
372    
373            final CRSTransformation step1 = createTransformationStep( sourceCS, sourceGCS );
374            final CRSTransformation step2 = createTransformationStep( sourceGCS, targetCS );
375            return concatenate( step1, step2 );
376        }
377    
378        /**
379         * Creates a transformation between a projected and a geographic coordinate systems. This method is automatically
380         * invoked by {@link #createFromCoordinateSystems createFromCoordinateSystems(...)}. The default implementation
381         * returns <code>{@link #createTransformationStep(GeographicCRS, ProjectedCRS)
382         * createTransformationStep}(targetCS, sourceCS) inverse)</code>.
383         * 
384         * @param sourceCRS
385         *            Input coordinate system.
386         * @param targetCRS
387         *            Output coordinate system.
388         * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code> or <code>null</code>
389         *         if {@link ProjectedCRS#getGeographicCRS()}.equals targetCRS.
390         * @throws TransformationException
391         *             if no transformation path has been found.
392         */
393        private CRSTransformation createTransformationStep( final ProjectedCRS sourceCRS, final GeographicCRS targetCRS )
394                                                                                                                         throws TransformationException {
395            LOG.logDebug( "Creating projected->geographic transformation: from (source): " + sourceCRS.getIdAndName()
396                          + " to(target): "
397                          + targetCRS.getIdAndName() );
398            CRSTransformation result = createTransformationStep( targetCRS, sourceCRS );
399            result.inverse();
400            return result;
401    
402        }
403    
404        /**
405         * Creates a transformation between two geocentric coordinate systems. This method is automatically invoked by
406         * {@link #createFromCoordinateSystems createFromCoordinateSystems(...)}. The default implementation can adjust for
407         * axis order and orientation, adjust for prime meridian, performs units conversion and apply Bursa Wolf
408         * transformation if needed.
409         * 
410         * @param sourceCS
411         *            Input coordinate system.
412         * @param targetCS
413         *            Output coordinate system.
414         * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>.
415         * @throws TransformationException
416         *             if no transformation path has been found.
417         */
418        private CRSTransformation createTransformationStep( final GeocentricCRS sourceCS, final GeocentricCRS targetCS )
419                                                                                                                        throws TransformationException {
420            LOG.logDebug( "Creating geocentric->geocetric transformation: from (source): " + sourceCS.getIdAndName()
421                          + " to(target): "
422                          + targetCS.getIdAndName() );
423            final GeodeticDatum sourceHD = sourceCS.getGeodeticDatum();
424            final GeodeticDatum targetHD = targetCS.getGeodeticDatum();
425            if ( sourceHD.equals( targetHD ) ) {
426                return null;
427                // final Matrix matrix = swapAndScaleAxis( sourceCS, targetCS );
428                // return createAffineTransform( sourceCS, targetCS, matrix );
429            }
430            if ( !PrimeMeridian.GREENWICH.equals( sourceHD.getPrimeMeridian() ) || !PrimeMeridian.GREENWICH.equals( targetHD.getPrimeMeridian() ) ) {
431                throw new TransformationException( "Rotation of prime meridian not yet implemented" );
432            }
433            // Transform between differents ellipsoids
434            // using Bursa Wolf parameters.
435            Matrix tmp = swapAndScaleAxis( sourceCS, GeocentricCRS.WGS84 );
436            Matrix4d step1 = null;
437            if ( tmp != null ) {
438                step1 = new Matrix4d();
439                tmp.get( step1 );
440            }
441            final Matrix4d step2 = getWGS84Parameters( sourceHD );
442            final Matrix4d step3 = getWGS84Parameters( targetHD );
443            tmp = swapAndScaleAxis( GeocentricCRS.WGS84, targetCS );
444            Matrix4d step4 = null;
445            if ( tmp != null ) {
446                step4 = new Matrix4d();
447                tmp.get( step4 );
448            }
449            if ( step1 == null && step2 == null && step3 == null && step4 == null ) {
450                LOG.logDebug( "The given geocentric crs's do not need a helmert transformation (but they are not equal), returning identity" );
451                step4 = new Matrix4d();
452                step4.setIdentity();
453            } else {
454                LOG.logDebug( "step1 matrix: \n " + step1 );
455                LOG.logDebug( "step2 matrix: \n " + step2 );
456                LOG.logDebug( "step3 matrix: \n " + step3 );
457                LOG.logDebug( "step4 matrix: \n " + step4 );
458                if ( step3 != null ) {
459                    step3.invert(); // Invert in place.
460                    LOG.logDebug( "step3 inverted matrix: \n " + step3 );
461                }
462                if ( step4 != null ) {
463                    if ( step3 != null ) {
464                        step4.mul( step3 ); // step4 = step4*step3
465                        LOG.logDebug( "step4 (after mul3): \n " + step4 );
466                    }
467                    if ( step2 != null ) {
468                        step4.mul( step2 ); // step4 = step4*step3*step2
469                        LOG.logDebug( "step4 (after mul2): \n " + step4 );
470                    }
471                    if ( step1 != null ) {
472                        step4.mul( step1 ); // step4 = step4*step3*step2*step1
473                    }
474                } else if ( step3 != null ) {
475                    step4 = step3;
476                    if ( step2 != null ) {
477                        step4.mul( step2 ); // step4 = step3*step2*step1
478                        LOG.logDebug( "step4 (after mul2): \n " + step4 );
479                    }
480                    if ( step1 != null ) {
481                        step4.mul( step1 ); // step4 = step3*step2*step1
482                    }
483                } else if ( step2 != null ) {
484                    step4 = step2;
485                    if ( step1 != null ) {
486                        step4.mul( step1 ); // step4 = step2*step1
487                    }
488                } else {
489                    step4 = step1;
490                }
491            }
492    
493            LOG.logDebug( "The resulting geo-geo matrix: from( " + sourceCS.getIdentifier()
494                          + ") to("
495                          + targetCS.getIdentifier()
496                          + ")\n "
497                          + step4 );
498            return new MatrixTransform( sourceCS, targetCS, step4, CRSTransformation.createFromTo( sourceCS.getIdentifier(), targetCS.getIdentifier() ), "Helmert-Transformation" );
499        }
500    
501        /**
502         * Concatenates two existing transforms.
503         * 
504         * @param tr1
505         *            The first transform to apply to points.
506         * @param tr2
507         *            The second transform to apply to points.
508         * @return The concatenated transform.
509         * 
510         */
511        private CRSTransformation createConcatenatedTransform( CRSTransformation tr1, CRSTransformation tr2 ) {
512            if ( tr1 == null ) {
513                return tr2;
514            }
515            if ( tr2 == null ) {
516                return tr1;
517            }
518            // if one of the two is an identity transformation, just return the other.
519            if ( tr1.isIdentity() ) {
520                return tr2;
521            }
522            if ( tr2.isIdentity() ) {
523                return tr1;
524            }
525    
526            /*
527             * If one transform is the inverse of the other, just return an identitiy transform.
528             */
529            if ( tr1.areInverse( tr2 ) ) {
530                LOG.logDebug( "Transformation1 and Transformation2 are inverse operations, returning null" );
531                return null;
532            }
533    
534            /*
535             * If both transforms use matrix, then we can create a single transform using the concatened matrix.
536             */
537            if ( tr1 instanceof MatrixTransform && tr2 instanceof MatrixTransform ) {
538                GMatrix m1 = ( (MatrixTransform) tr1 ).getMatrix();
539                GMatrix m2 = ( (MatrixTransform) tr2 ).getMatrix();
540                if ( m1 == null ) {
541                    if ( m2 == null ) {
542                        // both matrices are null, just return the identiy matrix.
543                        return new MatrixTransform( tr1.getSourceCRS(),
544                                                    tr1.getTargetCRS(),
545                                                    new GMatrix( tr2.getTargetDimension() + 1, tr1.getSourceDimension() + 1 ) );
546                    }
547                    return tr2;
548                } else if ( m2 == null ) {
549                    return tr1;
550                }
551                m2.mul( m1 );
552                return new MatrixTransform( tr1.getSourceCRS(), tr2.getTargetCRS(), m2 );
553            }
554    
555            /*
556             * If one or both math transform are instance of {@link ConcatenatedTransform}, then concatenate <code>tr1</code>
557             * or <code>tr2</code> with one of step transforms.
558             */
559            if ( tr1 instanceof ConcatenatedTransform ) {
560                final ConcatenatedTransform ctr = (ConcatenatedTransform) tr1;
561                tr1 = ctr.getFirstTransform();
562                tr2 = createConcatenatedTransform( ctr.getSecondTransform(), tr2 );
563            }
564            if ( tr2 instanceof ConcatenatedTransform ) {
565                final ConcatenatedTransform ctr = (ConcatenatedTransform) tr2;
566                tr1 = createConcatenatedTransform( tr1, ctr.getFirstTransform() );
567                tr2 = ctr.getSecondTransform();
568            }
569    
570            return new ConcatenatedTransform( tr1, tr2 );
571        }
572    
573        /**
574         * Creates an affine transform from a matrix.
575         * 
576         * @param matrix
577         *            The matrix used to define the affine transform.
578         * @return The affine transform.
579         * @throws TransformationException
580         *             if the matrix is not affine.
581         * 
582         */
583        private MatrixTransform createAffineTransform( CoordinateSystem source, CoordinateSystem target, final Matrix matrix )
584                                                                                                                              throws TransformationException {
585            if ( matrix == null ) {
586                return null;
587            }
588            if ( matrix.isAffine() ) {// Affine transform are square.
589                if ( matrix.getNumRow() == 3 && matrix.getNumCol() == 3 && !matrix.isIdentity() ) {
590                    LOG.logDebug( "Creating affineTransform of incoming matrix:\n" + matrix );
591                    Matrix3d tmp = matrix.toAffineTransform();
592                    LOG.logDebug( "Resulting AffineTransform is:\n" + tmp );
593                    return new MatrixTransform( source, target, tmp );
594                }
595                return new MatrixTransform( source, target, matrix );
596            }
597            throw new TransformationException( "Given matrix is not affine, cannot continue" );
598        }
599    
600        /**
601         * Returns the WGS84 parameters as an affine transform, or <code>null</code> if not available.
602         */
603        private Matrix4d getWGS84Parameters( final GeodeticDatum datum ) {
604            final WGS84ConversionInfo info = datum.getWGS84Conversion();
605            if ( info != null && !( new WGS84ConversionInfo( "test" ) ).equals( info ) ) {
606                return info.getAsAffineTransform();
607            }
608            return null;
609        }
610    
611        /**
612         * Concatenate two transformation steps.
613         * 
614         * @param step1
615         *            The first step, or <code>null</code> for the identity transform.
616         * @param step2
617         *            The second step, or <code>null</code> for the identity transform.
618         * @return A concatenated transform, or <code>null</code> if all arguments was nul.
619         */
620        private CRSTransformation concatenate( final CRSTransformation step1, final CRSTransformation step2 ) {
621            if ( step1 == null )
622                return step2;
623            if ( step2 == null )
624                return step1;
625    //        if ( !step1.getTargetCRS().equals( step2.getSourceCRS() ) ) {
626    //            throw new IllegalArgumentException( "The given crsTranformation can not be concatenated because the first tranformations targetCRS: " + step1.getTargetCRS()
627    //                                                                                                                                                         .getIdentifier()
628    //                                                + " does not match the second transformations sourceCRS: "
629    //                                                + step2.getSourceCRS().getIdentifier() );
630    //        }
631            return createConcatenatedTransform( step1, step2 );
632        }
633    
634        /**
635         * Concatenate three transformation steps.
636         * 
637         * @param step1
638         *            The first step, or <code>null</code> for the identity transform.
639         * @param step2
640         *            The second step, or <code>null</code> for the identity transform.
641         * @param step3
642         *            The third step, or <code>null</code> for the identity transform.
643         * @return A concatenated transform, or <code>null</code> if all arguments were <code>null</code>.
644         */
645        private CRSTransformation concatenate( final CRSTransformation step1, final CRSTransformation step2,
646                                               final CRSTransformation step3 ) {
647            if ( step1 == null ){
648                return concatenate( step2, step3 );
649            }
650            if ( step2 == null ){
651                return concatenate( step1, step3 );
652            }
653            if ( step3 == null ) {
654                return concatenate( step1, step2 );
655            }
656            return createConcatenatedTransform( step1, createConcatenatedTransform( step2, step3 ) );
657        }
658    
659        /**
660         * Returns an affine transform between two coordinate systems. Only units and axis order (e.g. transforming from
661         * (NORTH,WEST) to (EAST,NORTH)) are taken in account. Other attributes (especially the datum) must be checked
662         * before invoking this method.
663         * 
664         * @param sourceCS
665         *            The source coordinate system. If <code>null</code>, then (x,y,z,t) axis order is assumed.
666         * @param targetCS
667         *            The target coordinate system. If <code>null</code>, then (x,y,z,t) axis order is assumed.
668         */
669        private Matrix swapAndScaleAxis( final CoordinateSystem sourceCS, final CoordinateSystem targetCS )
670                                                                                                           throws TransformationException {
671            LOG.logDebug( "Creating swap matrix from: " + sourceCS.getIdAndName() + " to: " + targetCS.getIdAndName() );
672            final Axis[] sourceAxis = sourceCS.getAxis();
673            final Axis[] targetAxis = targetCS.getAxis();
674            final Matrix matrix;
675            try {
676                matrix = Matrix.createAffineTransform( sourceAxis, targetAxis );
677            } catch ( RuntimeException e ) {
678                throw new TransformationException( sourceCS, targetCS, e.getMessage() );
679            }
680            // Convert units if necessary
681            final int dimension = matrix.getNumRow() - 1;
682            for ( int i = 0; i < dimension; i++ ) {
683                final Unit sourceUnit = sourceAxis[i].getUnits();
684                final Unit targetUnit = targetAxis[i].getUnits();
685                double offset = 0;
686                double scale = 1;
687                if ( !sourceUnit.equals( targetUnit ) ) {
688                    offset = targetUnit.convert( 0, sourceUnit );
689                    scale = targetUnit.convert( 1, sourceUnit ) - offset;
690                }
691                matrix.setElement( i, i, scale * matrix.getElement( i, i ) );
692                matrix.setElement( i, dimension, scale * matrix.getElement( i, dimension ) + offset );
693            }
694            if ( matrix.isIdentity() ) {
695                return null;
696            }
697            return matrix;
698        }
699    
700        /**
701         * Returns an affine transform between two geographic coordinate systems. Only units, axis order (e.g. transforming
702         * from (NORTH,WEST) to (EAST,NORTH)) and prime meridian are taken in account. Other attributes (especially the
703         * datum) must be checked before invoking this method.
704         * 
705         * @param sourceCS
706         *            The source coordinate system.
707         * @param targetCS
708         *            The target coordinate system.
709         */
710        private Matrix swapAndScaleGeoAxis( final GeographicCRS sourceCS, final GeographicCRS targetCS )
711                                                                                                        throws TransformationException {
712            LOG.logDebug( "Creating geo swap matrix from: " + sourceCS.getIdAndName() + " to: " + targetCS.getIdAndName() );
713            final Matrix matrix = swapAndScaleAxis( sourceCS, targetCS );
714            if ( matrix != null ) {
715                Axis[] targetAxis = targetCS.getAxis();
716                final int lastMatrixColumn = matrix.getNumCol() - 1;
717                for ( int i = targetCS.getDimension(); --i >= 0; ) {
718                    // Find longitude, and apply a translation if prime meridians are different.
719                    final int orientation = targetAxis[i].getOrientation();
720                    if ( Axis.AO_EAST == Math.abs( orientation ) ) {
721                        final Unit unit = targetAxis[i].getUnits();
722                        final double sourceLongitude = sourceCS.getGeodeticDatum().getPrimeMeridian().getLongitude( unit );
723                        final double targetLongitude = targetCS.getGeodeticDatum().getPrimeMeridian().getLongitude( unit );
724                        if ( Math.abs( sourceLongitude - targetLongitude ) > ProjectionUtils.EPS11 ) {
725                            double translation = targetLongitude - sourceLongitude;
726                            if ( Axis.AO_WEST == orientation ) {
727                                translation = -translation;
728                            }
729                            // add the translation to the matrix translate element of this axis
730                            matrix.setElement( i, lastMatrixColumn, matrix.getElement( i, lastMatrixColumn ) - translation );
731                        }
732                    }
733                }
734            }
735            return matrix;
736        }
737    }