001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/model/crs/GeoTransformer.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005       Department of Geography, University of Bonn
006     and
007       lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035    ----------------------------------------------------------------------------*/
036    package org.deegree.model.crs;
037    
038    import java.awt.RenderingHints;
039    import java.awt.image.BufferedImage;
040    import java.awt.image.renderable.ParameterBlock;
041    import java.security.InvalidParameterException;
042    import java.util.ArrayList;
043    import java.util.List;
044    import java.util.Map;
045    import java.util.Set;
046    
047    import javax.media.jai.ImageLayout;
048    import javax.media.jai.Interpolation;
049    import javax.media.jai.InterpolationNearest;
050    import javax.media.jai.JAI;
051    import javax.media.jai.WarpPolynomial;
052    import javax.vecmath.Point3d;
053    
054    import org.deegree.crs.coordinatesystems.CoordinateSystem;
055    import org.deegree.crs.exceptions.TransformationException;
056    import org.deegree.crs.transformations.Transformation;
057    import org.deegree.crs.transformations.TransformationFactory;
058    import org.deegree.framework.log.ILogger;
059    import org.deegree.framework.log.LoggerFactory;
060    import org.deegree.graphics.transformation.GeoTransform;
061    import org.deegree.graphics.transformation.WorldToScreenTransform;
062    import org.deegree.i18n.Messages;
063    import org.deegree.model.coverage.grid.AbstractGridCoverage;
064    import org.deegree.model.coverage.grid.GridCoverage;
065    import org.deegree.model.coverage.grid.ImageGridCoverage;
066    import org.deegree.model.feature.Feature;
067    import org.deegree.model.feature.FeatureCollection;
068    import org.deegree.model.feature.FeatureProperty;
069    import org.deegree.model.spatialschema.Curve;
070    import org.deegree.model.spatialschema.CurveSegment;
071    import org.deegree.model.spatialschema.Envelope;
072    import org.deegree.model.spatialschema.Geometry;
073    import org.deegree.model.spatialschema.GeometryException;
074    import org.deegree.model.spatialschema.GeometryFactory;
075    import org.deegree.model.spatialschema.MultiCurve;
076    import org.deegree.model.spatialschema.MultiPoint;
077    import org.deegree.model.spatialschema.MultiSurface;
078    import org.deegree.model.spatialschema.Point;
079    import org.deegree.model.spatialschema.Position;
080    import org.deegree.model.spatialschema.Surface;
081    import org.deegree.model.spatialschema.SurfacePatch;
082    import org.deegree.ogcbase.OGCException;
083    import org.deegree.ogcwebservices.wcs.WCSException;
084    import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering;
085    import org.deegree.ogcwebservices.wcs.describecoverage.DomainSet;
086    
087    /**
088     * class for transforming deegree geometries to new coordinate reference systems.
089     *
090     * <p>
091     * ------------------------------------------------------------
092     * </p>
093     *
094     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
095     * @author last edited by: $Author: mschneider $
096     *
097     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
098     */
099    public class GeoTransformer {
100    
101        private static ILogger LOG_TRANSORM = LoggerFactory.getLogger( GeoTransformer.class.getCanonicalName()
102                                                                       + ".TransformLogger" );
103    
104        private static ILogger LOG = LoggerFactory.getLogger( GeoTransformer.class );
105    
106        private CoordinateSystem targetCRS = null;
107    
108        private org.deegree.model.crs.CoordinateSystem targetCRSWrapper = null;
109    
110        private Transformation definedTransformation = null;
111    
112        /**
113         * Creates a new GeoTransformer object.
114         *
115         * @param targetCRS
116         * @throws InvalidParameterException
117         *             if the given parameter is null.
118         */
119        public GeoTransformer( org.deegree.model.crs.CoordinateSystem targetCRS ) throws InvalidParameterException {
120            if ( targetCRS == null ) {
121                throw new InvalidParameterException( Messages.getMessage( "CRS_PARAMETER_NOT_NULL",
122                                                                          "GeoTransformer(CoordinateSystem)", "targetCRS" ) );
123            }
124            this.targetCRS = targetCRS.getCRS();
125            targetCRSWrapper = targetCRS;
126        }
127    
128        /**
129         * Creates a new GeoTransformer object.
130         *
131         * @param targetCRS
132         * @throws InvalidParameterException
133         *             if the given parameter is null.
134         */
135        public GeoTransformer( CoordinateSystem targetCRS ) {
136            if ( targetCRS == null ) {
137                throw new InvalidParameterException( Messages.getMessage( "CRS_PARAMETER_NOT_NULL",
138                                                                          "GeoTransformer(CoordinateSystem)", "targetCRS" ) );
139            }
140            this.targetCRS = targetCRS;
141        }
142    
143        /**
144         * @param definedTransformation
145         *            to use instead of the CRSFactory.
146         */
147        public GeoTransformer( Transformation definedTransformation ) {
148            if ( definedTransformation == null ) {
149                throw new InvalidParameterException( Messages.getMessage( "CRS_PARAMETER_NOT_NULL",
150                                                                          "GeoTransformer(CRSTransformation)", "targetCRS" ) );
151            }
152            targetCRS = definedTransformation.getTargetCRS();
153            targetCRSWrapper = new org.deegree.model.crs.CoordinateSystem( targetCRS );
154            this.definedTransformation = definedTransformation;
155        }
156    
157        /**
158         * Creates a new GeoTransformer object, with the given id as the target CRS.
159         *
160         * @param targetCRS
161         *            an identifier to which all other CRS's shall be transformed.
162         * @throws UnknownCRSException
163         *             if the given crs name could not be mapped to a valid (configured) crs.
164         * @throws InvalidParameterException
165         *             if the given parameter is null.
166         */
167        public GeoTransformer( String targetCRS ) throws UnknownCRSException, InvalidParameterException {
168            this( CRSFactory.create( targetCRS ) );
169        }
170    
171        /**
172         * transforms a GridCoverage into another coordinate reference system.
173         *
174         * @param coverage
175         *            grid coverage to definedTransformation
176         * @param targetBBOX
177         *            envelope for the target coverage
178         * @param dstWidth
179         *            width of the output coverage in pixel
180         * @param dstHeight
181         *            height of the output coverage in pixel
182         * @param refPointsGridSize
183         *            size of the grid used to calculate polynoms coefficients. E.g. 2 -&lg; 4 points, 3 -&lg; 9 points ...<br>
184         *            Must be &lg;= 2. Accuracy of coefficients increase with size of the grid. Speed decreases with size of
185         *            the grid.
186         * @param degree
187         *            The degree of the polynomial is supplied as an argument.
188         * @param interpolation
189         *            interpolation method for warping the passed coverage. Can be <code>null</code>. In this case 'Nearest
190         *            Neighbor' will be used as default
191         * @return a transformed GridCoverage.
192         * @throws CRSTransformationException
193         *             if the gridCoverage could not be created or the transformation failed
194         */
195        public GridCoverage transform( AbstractGridCoverage coverage, Envelope targetBBOX, int dstWidth, int dstHeight,
196                                       int refPointsGridSize, int degree, Interpolation interpolation )
197                                throws CRSTransformationException {
198    
199            BufferedImage img = coverage.getAsImage( -1, -1 );
200            Position min = coverage.getEnvelope().getMin();
201            Position max = coverage.getEnvelope().getMax();
202    
203            // create transformation object to definedTransformation reference points
204            // from the target CRS to the source (native) CRS
205            org.deegree.model.crs.CoordinateSystem crs = coverage.getCoordinateReferenceSystem();
206    
207            Envelope sourceBBOX = GeometryFactory.createEnvelope( min.getX(), min.getY(), max.getX(), max.getY(), crs );
208    
209            img = transform( img, sourceBBOX, targetBBOX, dstWidth, dstHeight, refPointsGridSize, degree, interpolation );
210    
211            // create a new GridCoverage from the warp result.
212            // because warping only can be performed on images the
213            // resulting GridCoverage will be an instance of ImageGridCoverage
214            CoverageOffering oldCO = coverage.getCoverageOffering();
215            CoverageOffering coverageOffering = null;
216            if ( oldCO != null ) {
217                try {
218                    DomainSet ds = oldCO.getDomainSet();
219                    ds.getSpatialDomain().setEnvelops( new Envelope[] { targetBBOX } );
220                    coverageOffering = new CoverageOffering( oldCO.getName(), oldCO.getLabel(), oldCO.getDescription(),
221                                                             oldCO.getMetadataLink(), oldCO.getLonLatEnvelope(),
222                                                             oldCO.getKeywords(), ds, oldCO.getRangeSet(),
223                                                             oldCO.getSupportedCRSs(), oldCO.getSupportedFormats(),
224                                                             oldCO.getSupportedInterpolations(), oldCO.getExtension() );
225                } catch ( WCSException e ) {
226                    throw new CRSTransformationException( Messages.getMessage( "CRS_CO_CREATION_ERROR",
227                                                                               crs.getIdentifier(),
228                                                                               targetCRS.getIdentifier(), e.getMessage() ),
229                                                          e );
230                } catch ( OGCException e ) {
231                    throw new CRSTransformationException( Messages.getMessage( "CRS_CO_CREATION_ERROR",
232                                                                               crs.getIdentifier(),
233                                                                               targetCRS.getIdentifier(), e.getMessage() ),
234                                                          e );
235                }
236            }
237    
238            return new ImageGridCoverage( coverageOffering, targetBBOX, img );
239        }
240    
241        /**
242         * transforms an image into another coordinate reference system.
243         *
244         * @param img
245         *            the image to definedTransformation
246         * @param sourceBBOX
247         *            envelope of the source image
248         * @param targetBBOX
249         *            envelope for the target image
250         * @param dstWidth
251         *            width of the output image in pixel
252         * @param dstHeight
253         *            height of the output image in pixel
254         * @param refPointsGridSize
255         *            size of the grid used to calculate polynoms coefficients. E.g. 2 -&lg; 4 points, 3 -&lg; 9 points ...<br>
256         *            Must be &lg;= 2. Accuracy of coefficients increase with size of the grid. Speed decreases with size of
257         *            the grid.
258         * @param degree
259         *            The degree of the polynomial is supplied as an argument.
260         * @param interpolation
261         *            interpolation method for warping the passed image. Can be <code>null</code>. In this case 'Nearest
262         *            Neighbor' will be used as default
263         * @return a transformed image.
264         * @throws CRSTransformationException
265         *             if the image could not be created or the transformation failed
266         */
267        public BufferedImage transform( BufferedImage img, Envelope sourceBBOX, Envelope targetBBOX, int dstWidth,
268                                        int dstHeight, int refPointsGridSize, int degree, Interpolation interpolation )
269                                throws CRSTransformationException {
270    
271            // create transformation object to definedTransformation reference points
272            // from the target CRS to the source (native) CRS
273            org.deegree.model.crs.CoordinateSystem crs = sourceBBOX.getCoordinateSystem();
274            // org.deegree.model.crs.CoordinateSystem targetMCRS = new org.deegree.model.crs.CoordinateSystem( targetCRS,
275            // null );
276    
277            GeoTransform sourceGT = new WorldToScreenTransform( sourceBBOX.getMin().getX(), sourceBBOX.getMin().getY(),
278                                                                sourceBBOX.getMax().getX(), sourceBBOX.getMax().getY(), 0,
279                                                                0, img.getWidth() - 1, img.getHeight() - 1 );
280            GeoTransform targetGT = new WorldToScreenTransform( targetBBOX.getMin().getX(), targetBBOX.getMin().getY(),
281                                                                targetBBOX.getMax().getX(), targetBBOX.getMax().getY(), 0,
282                                                                0, dstWidth - 1, dstHeight - 1 );
283    
284            // create/calculate reference points
285            float dx = ( dstWidth - 1 ) / (float) ( refPointsGridSize - 1 );
286            float dy = ( dstHeight - 1 ) / (float) ( refPointsGridSize - 1 );
287            float[] srcCoords = new float[refPointsGridSize * refPointsGridSize * 2];
288            float[] targetCoords = new float[refPointsGridSize * refPointsGridSize * 2];
289            int k = 0;
290    
291            GeoTransformer sourceCoordGT = new GeoTransformer( crs );
292            for ( int i = 0; i < refPointsGridSize; i++ ) {
293                for ( int j = 0; j < refPointsGridSize; j++ ) {
294                    targetCoords[k] = i * dx;
295                    targetCoords[k + 1] = j * dy;
296                    double x = targetGT.getSourceX( targetCoords[k] );
297                    double y = targetGT.getSourceY( targetCoords[k + 1] );
298                    Point point = GeometryFactory.createPoint( x, y, this.targetCRSWrapper );
299                    point = (Point) sourceCoordGT.transform( point );
300                    srcCoords[k] = (float) sourceGT.getDestX( point.getX() );
301                    srcCoords[k + 1] = (float) sourceGT.getDestY( point.getY() );
302                    // LOG.logDebug( String.format( "%.4f %.4f -> %.4f %.4f ",
303                    // srcCoords[k], srcCoords[k+1],
304                    // targetCoords[k], targetCoords[k+1]) );
305                    k += 2;
306                }
307            }
308    
309            // create warp object from reference points and desired interpolation
310            WarpPolynomial warp = WarpPolynomial.createWarp( srcCoords, 0, targetCoords, 0, srcCoords.length, 1f, 1f, 1f,
311                                                             1f, degree );
312    
313            if ( interpolation == null ) {
314                interpolation = new InterpolationNearest();
315            }
316    
317            // Create and perform the warp operation.
318            ParameterBlock pb = new ParameterBlock();
319            pb.addSource( img );
320            pb.add( warp );
321            pb.add( interpolation );
322            pb.add( new double[] { 0 } );
323    
324            // Limit output size, otherwise the result will be a bit larger
325            // (the polynomial warp will overlap the correct image border)
326            ImageLayout layout = new ImageLayout();
327            layout.setMinX( 0 );
328            layout.setMinY( 0 );
329            layout.setWidth( dstWidth );
330            layout.setHeight( dstHeight );
331            RenderingHints rh = new RenderingHints( JAI.KEY_IMAGE_LAYOUT, layout );
332    
333            return JAI.create( "warp", pb, rh ).getAsBufferedImage();
334        }
335    
336        /**
337         * transforms a GridCoverage into another coordinate reference system.
338         *
339         * @param coverage
340         *            grid coverage to definedTransformation
341         * @param refPointsGridSize
342         *            size of the grid used to calculate polynoms coefficients. E.g. 2 -&lg; 4 points, 3 -&lg; 9 points ...<br>
343         *            Must be &lg;= 2. Accuracy of coefficients increase with size of the grid. Speed decreases with size of
344         *            the grid.
345         * @param degree
346         *            The degree of the polynomial is supplied as an argument.
347         * @param interpolation
348         *            interpolation method for warping the passed coverage. Can be <code>null</code>. In this case 'Nearest
349         *            Neighbor' will be used as default
350         * @return a transformed GridCoverage.
351         * @throws CRSTransformationException
352         *             if the gridCoverage could not be created or the transformation failed
353         */
354        @Deprecated
355        public GridCoverage transform( AbstractGridCoverage coverage, int refPointsGridSize, int degree,
356                                       Interpolation interpolation )
357                                throws CRSTransformationException {
358    
359            BufferedImage img = coverage.getAsImage( -1, -1 );
360            Position min = coverage.getEnvelope().getMin();
361            Position max = coverage.getEnvelope().getMax();
362    
363            // create transformation object to definedTransformation reference points
364            // from the source (native) CRS to the target CRS
365            org.deegree.model.crs.CoordinateSystem crs = coverage.getCoordinateReferenceSystem();
366            Envelope sourceBBOX = GeometryFactory.createEnvelope( min.getX(), min.getY(), max.getX(), max.getY(), crs );
367            Envelope targetBBOX = transform( sourceBBOX, crs );
368    
369            GeoTransform sourceGT = new WorldToScreenTransform( sourceBBOX.getMin().getX(), sourceBBOX.getMin().getY(),
370                                                                sourceBBOX.getMax().getX(), sourceBBOX.getMax().getY(), 0,
371                                                                0, img.getWidth() - 1, img.getHeight() - 1 );
372            GeoTransform targetGT = new WorldToScreenTransform( targetBBOX.getMin().getX(), targetBBOX.getMin().getY(),
373                                                                targetBBOX.getMax().getX(), targetBBOX.getMax().getY(), 0,
374                                                                0, img.getWidth() - 1, img.getHeight() - 1 );
375    
376            // create/calculate reference points
377            float dx = img.getWidth() / (float) ( refPointsGridSize - 1 );
378            float dy = img.getHeight() / (float) ( refPointsGridSize - 1 );
379            float[] srcCoords = new float[refPointsGridSize * refPointsGridSize * 2];
380            float[] targetCoords = new float[refPointsGridSize * refPointsGridSize * 2];
381            int k = 0;
382            for ( int i = 0; i < refPointsGridSize; i++ ) {
383                for ( int j = 0; j < refPointsGridSize; j++ ) {
384                    srcCoords[k] = i * dx;
385                    srcCoords[k + 1] = j * dy;
386                    double x = sourceGT.getSourceX( srcCoords[k] );
387                    double y = sourceGT.getSourceY( srcCoords[k + 1] );
388                    Point point = GeometryFactory.createPoint( x, y, crs );
389                    point = (Point) transform( point );
390                    targetCoords[k] = (float) targetGT.getDestX( point.getX() );
391                    targetCoords[k + 1] = (float) targetGT.getDestY( point.getY() );
392                    k += 2;
393                }
394            }
395    
396            // create warp object from reference points and desired interpolation
397            WarpPolynomial warp = WarpPolynomial.createWarp( srcCoords, 0, targetCoords, 0, srcCoords.length, 1f, 1f, 1f,
398                                                             1f, degree );
399    
400            if ( interpolation == null ) {
401                interpolation = new InterpolationNearest();
402            }
403    
404            // Create and perform the warp operation.
405            ParameterBlock pb = new ParameterBlock();
406            pb.addSource( img );
407            pb.add( warp );
408            pb.add( interpolation );
409    
410            img = JAI.create( "warp", pb ).getAsBufferedImage();
411    
412            // create a new GridCoverage from the warp result.
413            // because warping only can be performed on images the
414            // resulting GridCoverage will be an instance of ImageGridCoverage
415            CoverageOffering oldCO = coverage.getCoverageOffering();
416            CoverageOffering coverageOffering = null;
417            if ( oldCO != null ) {
418                try {
419                    DomainSet ds = oldCO.getDomainSet();
420                    ds.getSpatialDomain().setEnvelops( new Envelope[] { targetBBOX } );
421                    coverageOffering = new CoverageOffering( oldCO.getName(), oldCO.getLabel(), oldCO.getDescription(),
422                                                             oldCO.getMetadataLink(), oldCO.getLonLatEnvelope(),
423                                                             oldCO.getKeywords(), ds, oldCO.getRangeSet(),
424                                                             oldCO.getSupportedCRSs(), oldCO.getSupportedFormats(),
425                                                             oldCO.getSupportedInterpolations(), oldCO.getExtension() );
426                } catch ( WCSException e ) {
427                    throw new CRSTransformationException( Messages.getMessage( "CRS_CO_CREATION_ERROR",
428                                                                               crs.getIdentifier(),
429                                                                               targetCRS.getIdentifier(), e.getMessage() ),
430                                                          e );
431                } catch ( OGCException e ) {
432                    throw new CRSTransformationException( Messages.getMessage( "CRS_CO_CREATION_ERROR",
433                                                                               crs.getIdentifier(),
434                                                                               targetCRS.getIdentifier(), e.getMessage() ),
435                                                          e );
436                }
437            }
438            return new ImageGridCoverage( coverageOffering, sourceBBOX, img );
439        }
440    
441        /**
442         * Transforms a <code>Envelope</code> to the target crs of the <code>GeoTransformer</code> instance
443         *
444         * @param envelope
445         *            to definedTransformation
446         * @param sourceCRS
447         *            CRS of the envelope
448         * @return the transformed envelope
449         * @throws CRSTransformationException
450         */
451        public Envelope transform( Envelope envelope, org.deegree.model.crs.CoordinateSystem sourceCRS )
452                                throws CRSTransformationException {
453    
454            Point min = GeometryFactory.createPoint( envelope.getMin().getX(), envelope.getMin().getY(), sourceCRS );
455            Point max = GeometryFactory.createPoint( envelope.getMax().getX(), envelope.getMax().getY(), sourceCRS );
456            min = (Point) transform( min );
457            max = (Point) transform( max );
458            // create bounding box with coordinates
459            return GeometryFactory.createEnvelope( min.getX(), min.getY(), max.getX(), max.getY(), targetCRSWrapper );
460        }
461    
462        /**
463         * Transforms a <code>Envelope</code> to the target crs of the <code>GeoTransformer</code> instance
464         *
465         * This transformation takes rotation and distortion into account when regardDistortion is true. Otherwise the
466         * transformed envelope may not contain the whole input envelope.
467         *
468         * @param envelope
469         *            to definedTransformation
470         * @param sourceCRS
471         *            CRS of the envelope
472         * @param regardDistortion
473         *
474         * @return the transformed envelope
475         * @throws CRSTransformationException
476         * @throws UnknownCRSException
477         */
478        public Envelope transform( Envelope envelope, String sourceCRS, boolean regardDistortion )
479                                throws CRSTransformationException, UnknownCRSException {
480            org.deegree.model.crs.CoordinateSystem crs = CRSFactory.create( sourceCRS );
481            return transform( envelope, crs, regardDistortion );
482        }
483    
484        /**
485         * Transforms a <code>Envelope</code> to the target crs of the <code>GeoTransformer</code> instance
486         *
487         * This transformation takes rotation and distortion into account when regardDistortion is true. Otherwise the
488         * transformed envelope may not contain the whole input envelope.
489         *
490         * @param envelope
491         *            to definedTransformation
492         * @param sourceCRS
493         *            CRS of the envelope
494         * @param regardDistortion
495         *
496         * @return the transformed envelope
497         * @throws CRSTransformationException
498         */
499        public Envelope transform( Envelope envelope, org.deegree.model.crs.CoordinateSystem sourceCRS,
500                                   boolean regardDistortion )
501                                throws CRSTransformationException {
502    
503            if ( !regardDistortion ) {
504                return transform( envelope, sourceCRS );
505            }
506            double x1 = envelope.getMin().getX();
507            double y1 = envelope.getMin().getY();
508            double x2 = envelope.getMax().getX();
509            double y2 = envelope.getMax().getY();
510    
511            double width = envelope.getWidth();
512            double height = envelope.getHeight();
513    
514            // definedTransformation with 8 points instead of only the min and max point
515            double[] coords = new double[] { x1, y1, x1, y2, x2, y2, x2, y1, x1, y1 + height, x1 + width, y1, x2,
516                                            y1 + height, x1 + width, y2 };
517    
518            Geometry envelopeGeom = null;
519            try {
520                envelopeGeom = GeometryFactory.createCurve( coords, 2, sourceCRS );
521            } catch ( GeometryException e ) {
522                throw new CRSTransformationException( Messages.getMessage( "CRS_TRANSFORMATION_ERROR",
523                                                                           sourceCRS.getIdentifier(),
524                                                                           targetCRS.getIdentifier(), e.getMessage() ), e );
525            }
526            envelopeGeom = transform( envelopeGeom );
527    
528            return envelopeGeom.getEnvelope();
529    
530        }
531    
532        /**
533         *
534         * @param sourceCRS
535         *            in which the given points are referenced.
536         * @param points
537         *            to definedTransformation.
538         * @return a list of transformed point3d's or an empty list if something went wrong, never <code>null</code>
539         * @throws CRSTransformationException
540         *             if no transformation could be created for the given source and target crs.
541         * @throws IllegalArgumentException
542         *             if the sourceCRS is <code>null</code>
543         */
544        public List<Point3d> transform( org.deegree.model.crs.CoordinateSystem sourceCRS, List<Point3d> points )
545                                throws CRSTransformationException, IllegalArgumentException {
546            if ( points == null || points.size() == 0 ) {
547                return new ArrayList<Point3d>();
548            }
549            if ( sourceCRS == null ) {
550                throw new IllegalArgumentException( Messages.getMessage( "CRS_NO_SOURCE_CRS" ) );
551            }
552            CoordinateSystem sCRS = sourceCRS.getCRS();
553            Transformation trans = checkOrCreateTransformation( sCRS );
554    
555            List<Point3d> result = new ArrayList<Point3d>( points.size() );
556            TransformationException exception = null;
557            try {
558                result = trans.doTransform( points );
559            } catch ( TransformationException te ) {
560                List<Point3d> tResult = te.getTransformedPoints();
561                if ( tResult != null && tResult.size() > 0 ) {
562                    result = tResult;
563                }
564                exception = te;
565            }
566    
567            if ( LOG_TRANSORM.isDebug() || LOG.isDebug() ) {
568                Map<Integer, String> errorMessages = null;
569                if ( exception != null ) {
570                    errorMessages = exception.getTransformErrors();
571                }
572                for ( int i = 0; i < points.size(); ++i ) {
573                    StringBuilder sb = new StringBuilder( 1000 );
574                    Point3d coord = points.get( i );
575                    Point3d resultCoord = result.get( i );
576                    if ( resultCoord == null ) {
577                        resultCoord = new Point3d( coord );
578                    }
579                    sb.append( trans.getSourceCRS().getIdentifier() );
580                    sb.append( ";" );
581                    sb.append( coord.x );
582                    sb.append( ";" );
583                    sb.append( coord.y );
584                    sb.append( ";" );
585                    if ( trans.getSourceDimension() == 3 ) {
586                        sb.append( coord.z );
587                        sb.append( ";" );
588                    }
589                    sb.append( trans.getTargetCRS().getIdentifier() );
590                    sb.append( ";" );
591                    sb.append( resultCoord.x );
592                    sb.append( ";" );
593                    sb.append( resultCoord.y );
594                    sb.append( ";" );
595                    if ( trans.getTargetDimension() == 3 ) {
596                        sb.append( resultCoord.z );
597                        sb.append( ";" );
598                    }
599                    String successString = "Success";
600                    if ( errorMessages != null ) {
601                        String tmp = errorMessages.get( new Integer( i ) );
602                        if ( tmp != null && !"".equals( tmp.trim() ) ) {
603                            successString = tmp;
604                        }
605                    }
606                    sb.append( successString );
607                    LOG_TRANSORM.logDebug( sb.toString() );
608                    // LOG.logDebug( sb.toString() );
609                }
610            }
611            if ( result == null ) {
612                result = new ArrayList<Point3d>();
613            }
614            return result;
615        }
616    
617        /**
618         * Transforms a <code>Envelope</code> to the target crs of the <code>GeoTransformer</code> instance
619         *
620         * @param envelope
621         *            to definedTransformation
622         * @param sourceCRS
623         *            CRS of the envelope
624         * @return the transformed envelope
625         * @throws CRSTransformationException
626         *             if the transformation did not succeed.
627         * @throws UnknownCRSException
628         *             if the given string is unknown to the CRSProvider
629         */
630        public Envelope transform( Envelope envelope, String sourceCRS )
631                                throws CRSTransformationException, UnknownCRSException {
632    
633            org.deegree.model.crs.CoordinateSystem crs = CRSFactory.create( sourceCRS );
634            return transform( envelope, crs );
635        }
636    
637        /**
638         * transforms all geometries contained within the passed {@link Feature} into the target CRS of a GeoTransformer
639         * instance. If a geometry was transformed the {@link Feature#setEnvelopesUpdated()} method will be called.
640         *
641         * @param feature
642         * @return the transformed geometries in the given Feature.
643         * @throws CRSTransformationException
644         */
645        public Feature transform( Feature feature )
646                                throws CRSTransformationException {
647            if ( feature != null ) {
648                FeatureProperty[] featureProperties = feature.getProperties();
649                if ( featureProperties != null ) {
650                    for ( FeatureProperty fp : featureProperties ) {
651                        if ( fp != null ) {
652                            Object value = fp.getValue();
653                            if ( value != null ) {
654                                if ( value instanceof Geometry ) {
655                                    Geometry geom = (Geometry) value;
656                                    if ( !targetCRSWrapper.equals( geom.getCoordinateSystem() ) ) {
657                                        fp.setValue( transform( geom ) );
658                                        feature.setEnvelopesUpdated();
659                                    }
660                                } else if ( value instanceof Feature ) {
661                                    transform( (Feature) value );
662                                }
663                            }
664                        }
665                    }
666                }
667            }
668            return feature;
669        }
670    
671        /**
672         * transforms all geometries contained within the passed {@link FeatureCollection} into the target CRS of a
673         * GeoTransformer instance.
674         *
675         * @param fc
676         *            the collection to definedTransformation
677         * @return the transformed geometries in the FeatureCollection
678         * @throws CRSTransformationException
679         *             if the transformation cannot be created or processed.
680         */
681        public FeatureCollection transform( FeatureCollection fc )
682                                throws CRSTransformationException {
683            for ( int i = 0; i < fc.size(); i++ ) {
684                transform( fc.getFeature( i ) );
685            }
686            // signal that the envelope properties might have been updated.
687            fc.setEnvelopesUpdated();
688            return fc;
689        }
690    
691        /**
692         * transforms the coordinates of a deegree geometry to the target coordinate reference system.
693         *
694         * @param geo
695         *            to be transformed
696         * @return the same geometry in a different crs.
697         * @throws CRSTransformationException
698         *             if the transformation between the source and target crs cannot be created.
699         * @throws IllegalArgumentException
700         *             if the coordinates system of the geometry is <code>null</code>
701         */
702        public Geometry transform( Geometry geo )
703                                throws CRSTransformationException, IllegalArgumentException {
704    
705            if ( geo.getCoordinateSystem() == null ) {
706                throw new IllegalArgumentException( Messages.getMessage( "CRS_GEOMETRY_HAS_NO_CRS" ) );
707            }
708    
709            return transform( geo, geo.getCoordinateSystem().getCRS() );
710        }
711    
712        /**
713         * @param geo
714         * @param sourceCRS
715         * @return a transformed geometry
716         * @throws CRSTransformationException
717         */
718        public Geometry transform( Geometry geo, CoordinateSystem sourceCRS )
719                                throws CRSTransformationException {
720            Transformation trans = checkOrCreateTransformation( sourceCRS );
721    
722            try {
723                if ( geo instanceof Point ) {
724                    geo = transform( (Point) geo, trans );
725                } else if ( geo instanceof Curve ) {
726                    geo = transform( (Curve) geo, trans );
727                } else if ( geo instanceof Surface ) {
728                    geo = transform( (Surface) geo, trans );
729                } else if ( geo instanceof MultiPoint ) {
730                    geo = transform( (MultiPoint) geo, trans );
731                } else if ( geo instanceof MultiCurve ) {
732                    geo = transform( (MultiCurve) geo, trans );
733                } else if ( geo instanceof MultiSurface ) {
734                    geo = transform( (MultiSurface) geo, trans );
735                }
736            } catch ( GeometryException ge ) {
737                throw new CRSTransformationException( Messages.getMessage( "CRS_TRANSFORMATION_ERROR",
738                                                                           sourceCRS.getIdentifier(),
739                                                                           targetCRSWrapper.getIdentifier(),
740                                                                           ge.getMessage() ), ge );
741            }
742            return geo;
743        }
744    
745        /**
746         * transforms the submitted curve to the target coordinate reference system
747         *
748         * @throws GeometryException
749         */
750        private Geometry transform( Curve geo, Transformation trans )
751                                throws GeometryException {
752            CurveSegment[] newcus = new CurveSegment[geo.getNumberOfCurveSegments()];
753            for ( int i = 0; i < geo.getNumberOfCurveSegments(); i++ ) {
754                CurveSegment cus = geo.getCurveSegmentAt( i );
755                Position[] pos = cus.getPositions();
756                pos = transform( pos, trans );
757                newcus[i] = GeometryFactory.createCurveSegment( pos, targetCRSWrapper );
758            }
759            return GeometryFactory.createCurve( newcus );
760        }
761    
762        /**
763         * transforms the submitted multi curve to the target coordinate reference system
764         *
765         * @throws GeometryException
766         */
767        private Geometry transform( MultiCurve geo, Transformation trans )
768                                throws GeometryException {
769            Curve[] curves = new Curve[geo.getSize()];
770            for ( int i = 0; i < geo.getSize(); i++ ) {
771                curves[i] = (Curve) transform( geo.getCurveAt( i ), trans );
772            }
773            return GeometryFactory.createMultiCurve( curves );
774        }
775    
776        /**
777         * transforms the submitted multi point to the target coordinate reference system
778         */
779        private Geometry transform( MultiPoint geo, Transformation trans ) {
780            Point[] points = new Point[geo.getSize()];
781            for ( int i = 0; i < geo.getSize(); i++ ) {
782                points[i] = (Point) transform( geo.getPointAt( i ), trans );
783            }
784            return GeometryFactory.createMultiPoint( points );
785        }
786    
787        /**
788         * transforms the submitted multi surface to the target coordinate reference system
789         *
790         * @throws GeometryException
791         */
792        private Geometry transform( MultiSurface geo, Transformation trans )
793                                throws GeometryException {
794            Surface[] surfaces = new Surface[geo.getSize()];
795            for ( int i = 0; i < geo.getSize(); i++ ) {
796                surfaces[i] = (Surface) transform( geo.getSurfaceAt( i ), trans );
797            }
798            return GeometryFactory.createMultiSurface( surfaces );
799        }
800    
801        /**
802         * transforms the submitted point to the target coordinate reference system
803         */
804        private Geometry transform( Point geo, Transformation trans ) {
805            Point3d coord = geo.getPosition().getAsPoint3d();
806            Point3d result = new Point3d( coord );
807            TransformationException exception = null;
808            try {
809                result = trans.doTransform( coord );
810            } catch ( TransformationException te ) {
811                List<Point3d> tResult = te.getTransformedPoints();
812                if ( tResult != null && tResult.size() > 0 ) {
813                    result = tResult.get( 0 );
814                }
815                exception = te;
816            }
817    
818            if ( LOG_TRANSORM.isDebug() || LOG.isDebug() ) {
819                StringBuilder sb = new StringBuilder( trans.getSourceCRS().getIdentifier() );
820                sb.append( ";" );
821                sb.append( coord.x );
822                sb.append( ";" );
823                sb.append( coord.y );
824                sb.append( ";" );
825                if ( trans.getSourceDimension() == 3 ) {
826                    sb.append( coord.z );
827                    sb.append( ";" );
828                }
829                sb.append( trans.getTargetCRS().getIdentifier() );
830                sb.append( ";" );
831                sb.append( result.x );
832                sb.append( ";" );
833                sb.append( result.y );
834                sb.append( ";" );
835                if ( trans.getTargetDimension() == 3 ) {
836                    sb.append( result.z );
837                    sb.append( ";" );
838                }
839                if ( exception != null ) {
840                    Map<Integer, String> messages = exception.getTransformErrors();
841                    if ( !messages.isEmpty() ) {
842                        Set<Integer> keys = messages.keySet();
843                        for ( Integer key : keys ) {
844                            sb.append( messages.get( key ) );
845                        }
846                    }
847                } else {
848                    sb.append( "Success" );
849                }
850                LOG_TRANSORM.logDebug( sb.toString() );
851                // LOG.logDebug( sb.toString() );
852            }
853            return GeometryFactory.createPoint( result.x, result.y, ( targetCRS.getDimension() == 3 ) ? result.z
854                                                                                                     : Double.NaN,
855                                                targetCRSWrapper );
856        }
857    
858        /**
859         * transforms an array of Positions to the target coordinate reference system
860         */
861        private Position[] transform( Position[] pos, Transformation trans ) {
862    
863            Position[] newpos = new Position[pos.length];
864            // boolean srcRad = trans.getSourceCRS().getUnits().equals( Unit.DEGREE );
865            // boolean targetRad = trans.getTargetCRS().getUnits().equals( Unit.DEGREE );
866    
867            List<Point3d> coords = new ArrayList<Point3d>( pos.length );
868            for ( Position p : pos ) {
869                // if ( srcRad ) {
870                // coords.add( new Point3d( Math.toRadians( p.getX() ), Math.toRadians( p.getY() ),
871                // p.getZ() ) );
872                // } else {
873                coords.add( new Point3d( p.getAsPoint3d() ) );
874                // }
875            }
876            // List<Point3d> result = trans.doTransform( coords );
877            List<Point3d> result = new ArrayList<Point3d>( coords.size() );
878            TransformationException exception = null;
879            try {
880                result = trans.doTransform( coords );
881            } catch ( TransformationException te ) {
882                List<Point3d> tResult = te.getTransformedPoints();
883                if ( tResult != null && tResult.size() > 0 ) {
884                    result = tResult;
885                }
886                exception = te;
887            }
888    
889            if ( LOG_TRANSORM.isDebug() || LOG.isDebug() ) {
890                Map<Integer, String> errorMessages = null;
891                if ( exception != null ) {
892                    errorMessages = exception.getTransformErrors();
893                }
894    
895                for ( int i = 0; i < coords.size(); ++i ) {
896                    StringBuilder sb = new StringBuilder( 1000 );
897                    Point3d coord = coords.get( i );
898                    Point3d resultCoord = result.get( i );
899                    if ( resultCoord == null ) {
900                        resultCoord = new Point3d( coord );
901                    }
902                    sb.append( trans.getSourceCRS().getIdentifier() );
903                    sb.append( ";" );
904                    sb.append( coord.x );
905                    sb.append( ";" );
906                    sb.append( coord.y );
907                    sb.append( ";" );
908                    if ( trans.getSourceDimension() == 3 ) {
909                        sb.append( coord.z );
910                        sb.append( ";" );
911                    }
912                    sb.append( trans.getTargetCRS().getIdentifier() );
913                    sb.append( ";" );
914                    sb.append( resultCoord.x );
915                    sb.append( ";" );
916                    sb.append( resultCoord.y );
917                    sb.append( ";" );
918                    if ( trans.getTargetDimension() == 3 ) {
919                        sb.append( resultCoord.z );
920                        sb.append( ";" );
921                    }
922                    String successString = "Success";
923                    if ( errorMessages != null ) {
924                        String tmp = errorMessages.get( new Integer( i ) );
925                        if ( tmp != null && !"".equals( tmp.trim() ) ) {
926                            successString = tmp;
927                        }
928                    }
929                    sb.append( successString );
930                    LOG_TRANSORM.logDebug( sb.toString() );
931                    // LOG.logDebug( sb.toString() );
932                }
933    
934            }
935            for ( int i = 0; i < result.size(); ++i ) {
936                Point3d p = result.get( i );
937                if ( p != null && i < newpos.length ) {
938                    newpos[i] = GeometryFactory.createPosition( p );
939                }
940            }
941            return newpos;
942        }
943    
944        /**
945         * transforms the submitted surface to the target coordinate reference system
946         *
947         * @throws GeometryException
948         *             if the surface cannot be retrieved or if the surface patch could not be created.
949         */
950        private Geometry transform( Surface geo, Transformation trans )
951                                throws GeometryException {
952            int cnt = geo.getNumberOfSurfacePatches();
953            SurfacePatch[] patches = new SurfacePatch[cnt];
954    
955            for ( int i = 0; i < cnt; i++ ) {
956                SurfacePatch p = geo.getSurfacePatchAt( i );
957                Position[] ex = p.getExteriorRing();
958                ex = transform( ex, trans );
959    
960                Position[][] innerRings = p.getInteriorRings();
961                Position[][] transformedInnerRings = null;
962    
963                if ( innerRings != null ) {
964                    transformedInnerRings = new Position[innerRings.length][];
965    
966                    for ( int k = 0; k < innerRings.length; k++ ) {
967                        transformedInnerRings[k] = transform( innerRings[k], trans );
968                    }
969                }
970    
971                patches[i] = GeometryFactory.createSurfacePatch( ex, transformedInnerRings, p.getInterpolation(),
972                                                                 this.targetCRSWrapper );
973            }
974    
975            // at the moment only polygons made of one patch are supported
976            return GeometryFactory.createSurface( patches[0] );
977        }
978    
979        /**
980         * Simple method to check for the CRS transformation to use.
981         *
982         * @param sourceCRS
983         * @throws CRSTransformationException
984         */
985        private synchronized Transformation checkOrCreateTransformation(
986                                                                         org.deegree.crs.coordinatesystems.CoordinateSystem sourceCRS )
987                                throws CRSTransformationException {
988            if ( definedTransformation == null
989                 || !( definedTransformation.getSourceCRS().equals( sourceCRS ) && definedTransformation.getTargetCRS().equals(
990                                                                                                                                targetCRS ) ) ) {
991                try {
992                    definedTransformation = TransformationFactory.getInstance().createFromCoordinateSystems( sourceCRS,
993                                                                                                             targetCRS );
994                } catch ( TransformationException e ) {
995                    throw new CRSTransformationException( e );
996                }
997            }
998            return definedTransformation;
999        }
1000    }