001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/model/crs/GeoTransformer.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     lat/lon GmbH
007     http://www.lat-lon.de
008    
009     This library is free software; you can redistribute it and/or
010     modify it under the terms of the GNU Lesser General Public
011     License as published by the Free Software Foundation; either
012     version 2.1 of the License, or (at your option) any later version.
013    
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    
019     You should have received a copy of the GNU Lesser General Public
020     License along with this library; if not, write to the Free Software
021     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
022    
023     Contact:
024    
025     Andreas Poth
026     lat/lon GmbH
027     Aennchenstr. 19
028     53115 Bonn
029     Germany
030     E-Mail: poth@lat-lon.de
031    
032     Klaus Greve
033     Department of Geography
034     University of Bonn
035     Meckenheimer Allee 166
036     53115 Bonn
037     Germany
038     E-Mail: klaus.greve@uni-bonn.de
039    
040    
041     ---------------------------------------------------------------------------*/
042    package org.deegree.model.crs;
043    
044    import java.awt.RenderingHints;
045    import java.awt.image.BufferedImage;
046    import java.awt.image.renderable.ParameterBlock;
047    import java.security.InvalidParameterException;
048    import java.util.ArrayList;
049    import java.util.List;
050    
051    import javax.media.jai.ImageLayout;
052    import javax.media.jai.Interpolation;
053    import javax.media.jai.InterpolationNearest;
054    import javax.media.jai.JAI;
055    import javax.media.jai.WarpPolynomial;
056    import javax.vecmath.Point3d;
057    
058    import org.deegree.crs.components.Unit;
059    import org.deegree.crs.configuration.CRSConfiguration;
060    import org.deegree.crs.configuration.CRSProvider;
061    import org.deegree.crs.coordinatesystems.CoordinateSystem;
062    import org.deegree.crs.exceptions.TransformationException;
063    import org.deegree.crs.projections.ProjectionUtils;
064    import org.deegree.crs.transformations.CRSTransformation;
065    import org.deegree.crs.transformations.TransformationFactory;
066    import org.deegree.graphics.transformation.GeoTransform;
067    import org.deegree.graphics.transformation.WorldToScreenTransform;
068    import org.deegree.i18n.Messages;
069    import org.deegree.model.coverage.grid.AbstractGridCoverage;
070    import org.deegree.model.coverage.grid.GridCoverage;
071    import org.deegree.model.coverage.grid.ImageGridCoverage;
072    import org.deegree.model.feature.Feature;
073    import org.deegree.model.feature.FeatureCollection;
074    import org.deegree.model.feature.FeatureProperty;
075    import org.deegree.model.spatialschema.Curve;
076    import org.deegree.model.spatialschema.CurveSegment;
077    import org.deegree.model.spatialschema.Envelope;
078    import org.deegree.model.spatialschema.Geometry;
079    import org.deegree.model.spatialschema.GeometryException;
080    import org.deegree.model.spatialschema.GeometryFactory;
081    import org.deegree.model.spatialschema.MultiCurve;
082    import org.deegree.model.spatialschema.MultiPoint;
083    import org.deegree.model.spatialschema.MultiSurface;
084    import org.deegree.model.spatialschema.Point;
085    import org.deegree.model.spatialschema.Position;
086    import org.deegree.model.spatialschema.Surface;
087    import org.deegree.model.spatialschema.SurfacePatch;
088    import org.deegree.ogcbase.OGCException;
089    import org.deegree.ogcwebservices.wcs.WCSException;
090    import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering;
091    import org.deegree.ogcwebservices.wcs.describecoverage.DomainSet;
092    import org.opengis.pt.PT_CoordinatePoint;
093    
094    /**
095     * class for transforming deegree geometries to new coordinate reference systems.
096     * 
097     * <p>
098     * ------------------------------------------------------------
099     * </p>
100     * 
101     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
102     * @author last edited by: $Author: rbezema $
103     * 
104     * @version $Revision: 11341 $, $Date: 2008-04-22 12:08:33 +0200 (Di, 22 Apr 2008) $
105     */
106    public class GeoTransformer {
107    
108        private final CRSProvider crsProvider;
109    
110        private CoordinateSystem targetCRS = null;
111    
112        private org.deegree.model.crs.CoordinateSystem targetCRSWrapper = null;
113    
114        /**
115         * Private constructor will get an instance of the CRSConfiguration, and from it the CRSProvider.
116         */
117        private GeoTransformer() {
118            CRSConfiguration crsConfig = CRSConfiguration.getCRSConfiguration();
119            crsProvider = crsConfig.getProvider();
120        }
121    
122        /**
123         * Creates a new GeoTransformer object.
124         * 
125         * @param targetCRS
126         * @throws InvalidParameterException
127         *             if the given parameter is null.
128         */
129        public GeoTransformer( org.deegree.model.crs.CoordinateSystem targetCRS ) throws InvalidParameterException {
130            this();
131            if ( targetCRS == null ) {
132                throw new InvalidParameterException( Messages.getMessage( "CRS_PARAMETER_NOT_NULL",
133                                                                          "GeoTransformer(CoordinateSystem)", "targetCRS" ) );
134            }
135            this.targetCRS = targetCRS.getCRS();
136            targetCRSWrapper = targetCRS;
137        }
138    
139        /**
140         * Creates a new GeoTransformer object, with the given id as the target CRS.
141         * 
142         * @param targetCRS
143         *            an identifier to which all other CRS's shall be transformed.
144         * @throws UnknownCRSException
145         *             if the given crs name could not be mapped to a valid (configured) crs.
146         * @throws InvalidParameterException
147         *             if the given parameter is null.
148         */
149        public GeoTransformer( String targetCRS ) throws UnknownCRSException, InvalidParameterException {
150            this();
151            if ( targetCRS == null ) {
152                throw new InvalidParameterException( Messages.getMessage( "CRS_PARAMETER_NOT_NULL",
153                                                                          "GeoTransformer(String)", "targetCRS" ) );
154            }
155            this.targetCRS = crsProvider.getCRSByID( targetCRS );
156            if ( this.targetCRS == null ) {
157                throw new UnknownCRSException( targetCRS );
158            }
159            targetCRSWrapper = new org.deegree.model.crs.CoordinateSystem( this.targetCRS, targetCRS );
160        }
161    
162        /**
163         * transforms a GridCoverage into another coordinate reference system.
164         * 
165         * @param coverage
166         *            grid coverage to transform
167         * @param targetBBOX
168         *            envelope for the target coverage
169         * @param dstWidth
170         *            width of the output coverage in pixel
171         * @param dstHeight
172         *            height of the output coverage in pixel
173         * @param refPointsGridSize
174         *            size of the grid used to calculate polynoms coefficients. E.g. 2 -&lg; 4 points, 3 -&lg; 9 points ...<br>
175         *            Must be &lg;= 2. Accuracy of coefficients increase with size of the grid. Speed decreases with size of
176         *            the grid.
177         * @param degree
178         *            The degree of the polynomial is supplied as an argument.
179         * @param interpolation
180         *            interpolation method for warping the passed coverage. Can be <code>null</code>. In this case
181         *            'Nearest Neighbor' will be used as default
182         * @return a transformed GridCoverage.
183         * @throws CRSTransformationException
184         *             if the gridCoverage could not be created or the transformation failed
185         */
186        public GridCoverage transform( AbstractGridCoverage coverage, Envelope targetBBOX, int dstWidth, int dstHeight,
187                                       int refPointsGridSize, int degree, Interpolation interpolation )
188                                throws CRSTransformationException {
189    
190            BufferedImage img = coverage.getAsImage( -1, -1 );
191            PT_CoordinatePoint min = coverage.getEnvelope().minCP;
192            PT_CoordinatePoint max = coverage.getEnvelope().maxCP;
193    
194            // create transformation object to transform reference points
195            // from the target CRS to the source (native) CRS
196            org.deegree.model.crs.CoordinateSystem crs = coverage.getCoordinateReferenceSystem();
197            org.deegree.model.crs.CoordinateSystem targetMCRS = new org.deegree.model.crs.CoordinateSystem( targetCRS, null );
198    
199            Envelope sourceBBOX = GeometryFactory.createEnvelope( min.ord[0], min.ord[1], max.ord[0], max.ord[1], crs );
200    
201            GeoTransform sourceGT = new WorldToScreenTransform( sourceBBOX.getMin().getX(), sourceBBOX.getMin().getY(),
202                                                                sourceBBOX.getMax().getX(), sourceBBOX.getMax().getY(), 0,
203                                                                0, img.getWidth() - 1, img.getHeight() - 1 );
204            GeoTransform targetGT = new WorldToScreenTransform( targetBBOX.getMin().getX(), targetBBOX.getMin().getY(),
205                                                                targetBBOX.getMax().getX(), targetBBOX.getMax().getY(), 0,
206                                                                0, dstWidth - 1, dstHeight - 1 );
207    
208            // create/calculate reference points
209            float dx = ( dstWidth - 1 ) / (float) ( refPointsGridSize - 1 );
210            float dy = ( dstHeight - 1 ) / (float) ( refPointsGridSize - 1 );
211            float[] srcCoords = new float[refPointsGridSize * refPointsGridSize * 2];
212            float[] targetCoords = new float[refPointsGridSize * refPointsGridSize * 2];
213            int k = 0;
214    
215            GeoTransformer sourceCoordGT = new GeoTransformer( crs );
216            for ( int i = 0; i < refPointsGridSize; i++ ) {
217                for ( int j = 0; j < refPointsGridSize; j++ ) {
218                    targetCoords[k] = i * dx;
219                    targetCoords[k + 1] = j * dy;
220                    double x = targetGT.getSourceX( targetCoords[k] );
221                    double y = targetGT.getSourceY( targetCoords[k + 1] );
222                    Point point = GeometryFactory.createPoint( x, y, targetMCRS );
223                    point = (Point) sourceCoordGT.transform( point );
224                    srcCoords[k] = (float) sourceGT.getDestX( point.getX() );
225                    srcCoords[k + 1] = (float) sourceGT.getDestY( point.getY() );
226                    // LOG.logDebug( String.format( "%.4f %.4f -> %.4f %.4f ",
227                    // srcCoords[k], srcCoords[k+1],
228                    // targetCoords[k], targetCoords[k+1]) );
229                    k += 2;
230                }
231            }
232    
233            // create warp object from reference points and desired interpolation
234            WarpPolynomial warp = WarpPolynomial.createWarp( srcCoords, 0, targetCoords, 0, srcCoords.length, 1f, 1f, 1f,
235                                                             1f, degree );
236    
237            if ( interpolation == null ) {
238                interpolation = new InterpolationNearest();
239            }
240    
241            // Create and perform the warp operation.
242            ParameterBlock pb = new ParameterBlock();
243            pb.addSource( img );
244            pb.add( warp );
245            pb.add( interpolation );
246    
247            // Limit output size, otherwise the result will be a bit larger
248            // (the polynomial warp will overlap the correct image border)
249            ImageLayout layout = new ImageLayout();
250            layout.setMinX( 0 );
251            layout.setMinY( 0 );
252            layout.setWidth( dstWidth );
253            layout.setHeight( dstHeight );
254            RenderingHints rh = new RenderingHints( JAI.KEY_IMAGE_LAYOUT, layout );
255    
256            img = JAI.create( "warp", pb, rh ).getAsBufferedImage();
257    
258            // create a new GridCoverage from the warp result.
259            // because warping only can be performed on images the
260            // resulting GridCoverage will be an instance of ImageGridCoverage
261            CoverageOffering oldCO = coverage.getCoverageOffering();
262            CoverageOffering coverageOffering = null;
263            if ( oldCO != null ) {
264                try {
265                    DomainSet ds = oldCO.getDomainSet();
266                    ds.getSpatialDomain().setEnvelops( new Envelope[] { targetBBOX } );
267                    coverageOffering = new CoverageOffering( oldCO.getName(), oldCO.getLabel(), oldCO.getDescription(),
268                                                             oldCO.getMetadataLink(), oldCO.getLonLatEnvelope(),
269                                                             oldCO.getKeywords(), ds, oldCO.getRangeSet(),
270                                                             oldCO.getSupportedCRSs(), oldCO.getSupportedFormats(),
271                                                             oldCO.getSupportedInterpolations(), oldCO.getExtension() );
272                } catch ( WCSException e ) {
273                    throw new CRSTransformationException( Messages.getMessage( "CRS_CO_CREATION_ERROR",
274                                                                               crs.getIdentifier(),
275                                                                               targetCRS.getIdentifier(), e.getMessage() ),
276                                                          e );
277                } catch ( OGCException e ) {
278                    throw new CRSTransformationException( Messages.getMessage( "CRS_CO_CREATION_ERROR",
279                                                                               crs.getIdentifier(),
280                                                                               targetCRS.getIdentifier(), e.getMessage() ),
281                                                          e );
282                }
283            }
284    
285            return new ImageGridCoverage( coverageOffering, sourceBBOX, img );
286        }
287    
288        /**
289         * transforms a GridCoverage into another coordinate reference system.
290         * 
291         * @param coverage
292         *            grid coverage to transform
293         * @param refPointsGridSize
294         *            size of the grid used to calculate polynoms coefficients. E.g. 2 -&lg; 4 points, 3 -&lg; 9 points ...<br>
295         *            Must be &lg;= 2. Accuracy of coefficients increase with size of the grid. Speed decreases with size of
296         *            the grid.
297         * @param degree
298         *            The degree of the polynomial is supplied as an argument.
299         * @param interpolation
300         *            interpolation method for warping the passed coverage. Can be <code>null</code>. In this case
301         *            'Nearest Neighbor' will be used as default
302         * @return a transformed GridCoverage.
303         * @throws CRSTransformationException
304         *             if the gridCoverage could not be created or the transformation failed
305         */
306        @Deprecated
307        public GridCoverage transform( AbstractGridCoverage coverage, int refPointsGridSize, int degree,
308                                       Interpolation interpolation )
309                                throws CRSTransformationException {
310    
311            BufferedImage img = coverage.getAsImage( -1, -1 );
312            PT_CoordinatePoint min = coverage.getEnvelope().minCP;
313            PT_CoordinatePoint max = coverage.getEnvelope().maxCP;
314    
315            // create transformation object to transform reference points
316            // from the source (native) CRS to the target CRS
317            org.deegree.model.crs.CoordinateSystem crs = coverage.getCoordinateReferenceSystem();
318            Envelope sourceBBOX = GeometryFactory.createEnvelope( min.ord[0], min.ord[1], max.ord[0], max.ord[1], crs );
319            Envelope targetBBOX = transform( sourceBBOX, crs );
320    
321            GeoTransform sourceGT = new WorldToScreenTransform( sourceBBOX.getMin().getX(), sourceBBOX.getMin().getY(),
322                                                                sourceBBOX.getMax().getX(), sourceBBOX.getMax().getY(), 0,
323                                                                0, img.getWidth() - 1, img.getHeight() - 1 );
324            GeoTransform targetGT = new WorldToScreenTransform( targetBBOX.getMin().getX(), targetBBOX.getMin().getY(),
325                                                                targetBBOX.getMax().getX(), targetBBOX.getMax().getY(), 0,
326                                                                0, img.getWidth() - 1, img.getHeight() - 1 );
327    
328            // create/calculate reference points
329            float dx = img.getWidth() / (float) ( refPointsGridSize - 1 );
330            float dy = img.getHeight() / (float) ( refPointsGridSize - 1 );
331            float[] srcCoords = new float[refPointsGridSize * refPointsGridSize * 2];
332            float[] targetCoords = new float[refPointsGridSize * refPointsGridSize * 2];
333            int k = 0;
334            for ( int i = 0; i < refPointsGridSize; i++ ) {
335                for ( int j = 0; j < refPointsGridSize; j++ ) {
336                    srcCoords[k] = i * dx;
337                    srcCoords[k + 1] = j * dy;
338                    double x = sourceGT.getSourceX( srcCoords[k] );
339                    double y = sourceGT.getSourceY( srcCoords[k + 1] );
340                    Point point = GeometryFactory.createPoint( x, y, crs );
341                    point = (Point) transform( point );
342                    targetCoords[k] = (float) targetGT.getDestX( point.getX() );
343                    targetCoords[k + 1] = (float) targetGT.getDestY( point.getY() );
344                    k += 2;
345                }
346            }
347    
348            // create warp object from reference points and desired interpolation
349            WarpPolynomial warp = WarpPolynomial.createWarp( srcCoords, 0, targetCoords, 0, srcCoords.length, 1f, 1f, 1f,
350                                                             1f, degree );
351    
352            if ( interpolation == null ) {
353                interpolation = new InterpolationNearest();
354            }
355    
356            // Create and perform the warp operation.
357            ParameterBlock pb = new ParameterBlock();
358            pb.addSource( img );
359            pb.add( warp );
360            pb.add( interpolation );
361    
362            img = JAI.create( "warp", pb ).getAsBufferedImage();
363    
364            // create a new GridCoverage from the warp result.
365            // because warping only can be performed on images the
366            // resulting GridCoverage will be an instance of ImageGridCoverage
367            CoverageOffering oldCO = coverage.getCoverageOffering();
368            CoverageOffering coverageOffering = null;
369            if ( oldCO != null ) {
370                try {
371                    DomainSet ds = oldCO.getDomainSet();
372                    ds.getSpatialDomain().setEnvelops( new Envelope[] { targetBBOX } );
373                    coverageOffering = new CoverageOffering( oldCO.getName(), oldCO.getLabel(), oldCO.getDescription(),
374                                                             oldCO.getMetadataLink(), oldCO.getLonLatEnvelope(),
375                                                             oldCO.getKeywords(), ds, oldCO.getRangeSet(),
376                                                             oldCO.getSupportedCRSs(), oldCO.getSupportedFormats(),
377                                                             oldCO.getSupportedInterpolations(), oldCO.getExtension() );
378                } catch ( WCSException e ) {
379                    throw new CRSTransformationException( Messages.getMessage( "CRS_CO_CREATION_ERROR",
380                                                                               crs.getIdentifier(),
381                                                                               targetCRS.getIdentifier(), e.getMessage() ),
382                                                          e );
383                } catch ( OGCException e ) {
384                    throw new CRSTransformationException( Messages.getMessage( "CRS_CO_CREATION_ERROR",
385                                                                               crs.getIdentifier(),
386                                                                               targetCRS.getIdentifier(), e.getMessage() ),
387                                                          e );
388                }
389            }
390            return new ImageGridCoverage( coverageOffering, sourceBBOX, img );
391        }
392    
393        /**
394         * transforms a <code>Envelope</code> to the target crs of the <code>GeoTransformer</code> instance
395         * 
396         * @param envelope
397         *            to transform
398         * @param sourceCRS
399         *            CRS of the envelope
400         * @return the transformed envelope
401         * @throws CRSTransformationException
402         */
403        public Envelope transform( Envelope envelope, org.deegree.model.crs.CoordinateSystem sourceCRS )
404                                throws CRSTransformationException {
405    
406            Point min = GeometryFactory.createPoint( envelope.getMin().getX(), envelope.getMin().getY(), sourceCRS );
407            Point max = GeometryFactory.createPoint( envelope.getMax().getX(), envelope.getMax().getY(), sourceCRS );
408            min = (Point) transform( min );
409            max = (Point) transform( max );
410            // create bounding box with coordinates
411            return GeometryFactory.createEnvelope( min.getX(), min.getY(), max.getX(), max.getY(), targetCRSWrapper );
412        }
413    
414        /**
415         * transfroms a <code>Envelope</code> to the target crs of the <code>GeoTransformer</code> instance
416         * 
417         * This transformation takes rotation and distortion into account when regardDistortion is true. Otherwise the
418         * transformed envelope may not contain the whole input envelope.
419         * 
420         * @param envelope
421         *            to transform
422         * @param sourceCRS
423         *            CRS of the envelope
424         * @param regardDistortion
425         * 
426         * @return the transformed envelope
427         * @throws CRSTransformationException
428         */
429        public Envelope transform( Envelope envelope, org.deegree.model.crs.CoordinateSystem sourceCRS,
430                                   boolean regardDistortion )
431                                throws CRSTransformationException {
432    
433            if ( !regardDistortion ) {
434                return transform( envelope, sourceCRS );
435            }
436            double x1 = envelope.getMin().getX();
437            double y1 = envelope.getMin().getY();
438            double x2 = envelope.getMax().getX();
439            double y2 = envelope.getMax().getY();
440    
441            double width = envelope.getWidth();
442            double height = envelope.getHeight();
443    
444            // transform with 8 points instead of only the min and max point
445            double[] coords = new double[] { x1, y1, x1, y2, x2, y2, x2, y1, x1, y1 + height, x1 + width, y1, x2,
446                                            y1 + height, x1 + width, y2 };
447    
448            Geometry envelopeGeom = null;
449            try {
450                envelopeGeom = GeometryFactory.createCurve( coords, 2, sourceCRS );
451            } catch ( GeometryException e ) {
452                throw new CRSTransformationException( Messages.getMessage( "CRS_TRANSFORMATION_ERROR",
453                                                                           sourceCRS.getIdentifier(),
454                                                                           targetCRS.getIdentifier(), e.getMessage() ), e );
455            }
456            envelopeGeom = transform( envelopeGeom );
457    
458            return envelopeGeom.getEnvelope();
459    
460        }
461    
462        /**
463         * Transforms a <code>Envelope</code> to the target crs of the <code>GeoTransformer</code> instance
464         * 
465         * @param envelope
466         *            to transform
467         * @param sourceCRS
468         *            CRS of the envelope
469         * @return the transformed envelope
470         * @throws CRSTransformationException
471         *             if the transformation did not succeed.
472         * @throws UnknownCRSException
473         *             if the given string is unknown to the CRSProvider
474         */
475        public Envelope transform( Envelope envelope, String sourceCRS )
476                                throws CRSTransformationException, UnknownCRSException {
477    
478            org.deegree.model.crs.CoordinateSystem crs = CRSFactory.create( sourceCRS );
479            return transform( envelope, crs );
480        }
481    
482        /**
483         * transforms all geometries contained within the passed {@link Feature} into the target CRS of a GeoTransformer
484         * instance. If a geometry was transformed the {@link Feature#setEnvelopesUpdated()} method will be called.
485         * 
486         * @param feature
487         * @return the transformed geometries in the given Feature.
488         * @throws CRSTransformationException
489         */
490        public Feature transform( Feature feature )
491                                throws CRSTransformationException {
492            if ( feature != null ) {
493                FeatureProperty[] featureProperties = feature.getProperties();
494                if ( featureProperties != null ) {
495                    for ( FeatureProperty fp : featureProperties ) {
496                        if ( fp != null ) {
497                            Object value = fp.getValue();
498                            if ( value != null ) {
499                                if ( value instanceof Geometry ) {
500                                    Geometry geom = (Geometry) value;
501                                    if ( !targetCRSWrapper.equals( geom.getCoordinateSystem() ) ) {
502                                        fp.setValue( transform( geom ) );
503                                        feature.setEnvelopesUpdated();
504                                    }
505                                } else if ( value instanceof Feature ) {
506                                    transform( (Feature) value );
507                                }
508                            }
509                        }
510                    }
511                }
512            }
513            return feature;
514        }
515    
516        /**
517         * transforms all geometries contained within the passed {@link FeatureCollection} into the target CRS of a
518         * GeoTransformer instance.
519         * 
520         * @param fc
521         *            the collection to transform
522         * @return the transformed geometries in the FeatureCollection
523         * @throws CRSTransformationException
524         *             if the transformation cannot be created or processed.
525         */
526        public FeatureCollection transform( FeatureCollection fc )
527                                throws CRSTransformationException {
528            for ( int i = 0; i < fc.size(); i++ ) {
529                transform( fc.getFeature( i ) );
530            }
531            // signal that the envelope properties might have been updated.
532            fc.setEnvelopesUpdated();
533            return fc;
534        }
535    
536        /**
537         * transforms the coodinates of a deegree geometry to the target coordinate reference system.
538         * 
539         * @param geo
540         *            to be transformed
541         * @return the same geometry in a different crs.
542         * @throws CRSTransformationException
543         *             if the transformation between the source and target crs cannot be created.
544         * @throws IllegalArgumentException
545         *             if the coordinates system of the geometry is <code>null</code>
546         */
547        public Geometry transform( Geometry geo )
548                                throws CRSTransformationException, IllegalArgumentException {
549    
550            if ( geo.getCoordinateSystem() == null ) {
551                throw new IllegalArgumentException( Messages.getMessage( "CRS_GEOMETRY_HAS_NO_CRS" ) );
552            }
553            CoordinateSystem sourceCRS = geo.getCoordinateSystem().getCRS();
554    
555            TransformationFactory factory = TransformationFactory.getInstance();
556            CRSTransformation trans = null;
557            try {
558                trans = factory.createFromCoordinateSystems( sourceCRS, targetCRS );
559            } catch ( TransformationException e ) {
560                throw new CRSTransformationException( e );
561            }
562            try {
563                if ( geo instanceof Point ) {
564                    geo = transform( (Point) geo, trans );
565                } else if ( geo instanceof Curve ) {
566                    geo = transform( (Curve) geo, trans );
567                } else if ( geo instanceof Surface ) {
568                    geo = transform( (Surface) geo, trans );
569                } else if ( geo instanceof MultiPoint ) {
570                    geo = transform( (MultiPoint) geo, trans );
571                } else if ( geo instanceof MultiCurve ) {
572                    geo = transform( (MultiCurve) geo, trans );
573                } else if ( geo instanceof MultiSurface ) {
574                    geo = transform( (MultiSurface) geo, trans );
575                }
576            } catch ( GeometryException ge ) {
577                throw new CRSTransformationException( Messages.getMessage( "CRS_TRANSFORMATION_ERROR",
578                                                                           sourceCRS.getIdentifier(),
579                                                                           targetCRSWrapper.getIdentifier(),
580                                                                           ge.getMessage() ), ge );
581            }
582            return geo;
583        }
584    
585        /**
586         * transforms the submitted curve to the target coordinate reference system
587         * 
588         * @throws GeometryException
589         */
590        private Geometry transform( Curve geo, CRSTransformation trans )
591                                throws GeometryException {
592            CurveSegment[] newcus = new CurveSegment[geo.getNumberOfCurveSegments()];
593            for ( int i = 0; i < geo.getNumberOfCurveSegments(); i++ ) {
594                CurveSegment cus = geo.getCurveSegmentAt( i );
595                Position[] pos = cus.getPositions();
596                pos = transform( pos, trans );
597                newcus[i] = GeometryFactory.createCurveSegment( pos, targetCRSWrapper );
598            }
599            return GeometryFactory.createCurve( newcus );
600        }
601    
602        /**
603         * transforms the submitted multi curve to the target coordinate reference system
604         * 
605         * @throws GeometryException
606         */
607        private Geometry transform( MultiCurve geo, CRSTransformation trans )
608                                throws GeometryException {
609            Curve[] curves = new Curve[geo.getSize()];
610            for ( int i = 0; i < geo.getSize(); i++ ) {
611                curves[i] = (Curve) transform( geo.getCurveAt( i ), trans );
612            }
613            return GeometryFactory.createMultiCurve( curves );
614        }
615    
616        /**
617         * transforms the submitted multi point to the target coordinate reference system
618         */
619        private Geometry transform( MultiPoint geo, CRSTransformation trans ) {
620            Point[] points = new Point[geo.getSize()];
621            for ( int i = 0; i < geo.getSize(); i++ ) {
622                points[i] = (Point) transform( geo.getPointAt( i ), trans );
623            }
624            return GeometryFactory.createMultiPoint( points );
625        }
626    
627        /**
628         * transforms the submitted multi surface to the target coordinate reference system
629         * 
630         * @throws GeometryException
631         */
632        private Geometry transform( MultiSurface geo, CRSTransformation trans )
633                                throws GeometryException {
634            Surface[] surfaces = new Surface[geo.getSize()];
635            for ( int i = 0; i < geo.getSize(); i++ ) {
636                surfaces[i] = (Surface) transform( geo.getSurfaceAt( i ), trans );
637            }
638            return GeometryFactory.createMultiSurface( surfaces );
639        }
640    
641        /**
642         * transforms the submitted point to the target coordinate reference system
643         */
644        private Geometry transform( Point geo, CRSTransformation trans ) {
645    
646            double[] din = geo.getAsArray();
647            /**
648             * Normalize points to fit in -180:180 and -90:90 if they are in degrees.
649             */
650            if ( geo.getCoordinateSystem().getCRS().getUnits().equals( Unit.RADIAN ) ) {
651                din[0] = ProjectionUtils.normalizeLongitude( Math.toRadians( din[0] ) );
652                din[1] = ProjectionUtils.normalizeLatitude( Math.toRadians( din[1] ) );
653            }
654    
655            Point3d coord = new Point3d( din[0], din[1], ( geo.getCoordinateSystem().getDimension() == 3 ) ? din[2]
656                                                                                                          : Double.NaN );
657            Point3d result = trans.doTransform( coord );
658            if ( targetCRS.getUnits().equals( Unit.RADIAN ) ) {
659                result.x = Math.toDegrees( result.x );
660                result.y = Math.toDegrees( result.y );
661            }
662            return GeometryFactory.createPoint( result.x, result.y, ( targetCRS.getDimension() == 3 ) ? result.z
663                                                                                                     : Double.NaN,
664                                                targetCRSWrapper );
665        }
666    
667        /**
668         * transforms an array of Positions to the target coordinate reference system
669         */
670        private Position[] transform( Position[] pos, CRSTransformation trans ) {
671    
672            Position[] newpos = new Position[pos.length];
673            boolean srcRad = trans.getSourceCRS().getUnits().equals( Unit.RADIAN );
674            boolean targetRad = trans.getTargetCRS().getUnits().equals( Unit.RADIAN );
675    
676            List<Point3d> coords = new ArrayList<Point3d>( pos.length );
677            for ( Position p : pos ) {
678                if ( srcRad ) {
679                    coords.add( new Point3d( Math.toRadians( p.getX() ), Math.toRadians( p.getY() ), p.getZ() ) );
680                } else {
681                    coords.add( new Point3d( p.getX(), p.getY(), p.getZ() ) );
682                }
683            }
684            List<Point3d> result = trans.doTransform( coords );
685            for ( int i = 0; i < result.size(); ++i ) {
686                Point3d p = result.get( i );
687                if ( p != null && i < newpos.length ) {
688                    if ( targetRad ) {
689                        newpos[i] = GeometryFactory.createPosition( Math.toDegrees( p.x ), Math.toDegrees( p.y ),
690                                                                    Double.NaN );
691                    } else {
692                        newpos[i] = GeometryFactory.createPosition( p.x, p.y, ( targetCRS.getDimension() == 3 ) ? p.z
693                                                                                                               : Double.NaN );
694                    }
695                }
696            }
697            return newpos;
698        }
699    
700        /**
701         * transforms the submitted surface to the target coordinate reference system
702         * 
703         * @throws GeometryException
704         *             if the surface cannot be retrieved or if the surface patch could not be created.
705         */
706        private Geometry transform( Surface geo, CRSTransformation trans )
707                                throws GeometryException {
708            int cnt = geo.getNumberOfSurfacePatches();
709            SurfacePatch[] patches = new SurfacePatch[cnt];
710    
711            for ( int i = 0; i < cnt; i++ ) {
712                SurfacePatch p = geo.getSurfacePatchAt( i );
713                Position[] ex = p.getExteriorRing();
714                ex = transform( ex, trans );
715    
716                Position[][] innerRings = p.getInteriorRings();
717                Position[][] transformedInnerRings = null;
718    
719                if ( innerRings != null ) {
720                    transformedInnerRings = new Position[innerRings.length][];
721    
722                    for ( int k = 0; k < innerRings.length; k++ ) {
723                        transformedInnerRings[k] = transform( innerRings[k], trans );
724                    }
725                }
726    
727                patches[i] = GeometryFactory.createSurfacePatch( ex, transformedInnerRings, p.getInterpolation(),
728                                                                 this.targetCRSWrapper );
729            }
730    
731            // at the moment only polygons made of one patch are supported
732            return GeometryFactory.createSurface( patches[0] );
733        }
734    
735    }