001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wpvs/utils/ResolutionStripe.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    
037    package org.deegree.ogcwebservices.wpvs.utils;
038    
039    import java.awt.BasicStroke;
040    import java.awt.Color;
041    import java.awt.Font;
042    import java.awt.Graphics2D;
043    import java.awt.Stroke;
044    import java.awt.font.TextLayout;
045    import java.awt.geom.Rectangle2D;
046    import java.awt.image.BufferedImage;
047    import java.io.File;
048    import java.io.IOException;
049    import java.util.ArrayList;
050    import java.util.Collection;
051    import java.util.HashMap;
052    import java.util.Iterator;
053    import java.util.List;
054    import java.util.Random;
055    import java.util.Set;
056    import java.util.concurrent.Callable;
057    
058    import javax.imageio.ImageIO;
059    import javax.media.j3d.OrderedGroup;
060    import javax.vecmath.Point3d;
061    import javax.vecmath.Vector3f;
062    
063    import org.deegree.framework.log.ILogger;
064    import org.deegree.framework.log.LoggerFactory;
065    import org.deegree.framework.util.MapUtils;
066    import org.deegree.framework.util.StringTools;
067    import org.deegree.i18n.Messages;
068    import org.deegree.model.crs.CoordinateSystem;
069    import org.deegree.model.spatialschema.Envelope;
070    import org.deegree.model.spatialschema.GeometryException;
071    import org.deegree.model.spatialschema.Position;
072    import org.deegree.model.spatialschema.Surface;
073    import org.deegree.model.spatialschema.WKTAdapter;
074    import org.deegree.ogcwebservices.OGCWebServiceException;
075    import org.deegree.ogcwebservices.wpvs.GetViewServiceInvoker;
076    import org.deegree.ogcwebservices.wpvs.WCSInvoker;
077    import org.deegree.ogcwebservices.wpvs.WFSInvoker;
078    import org.deegree.ogcwebservices.wpvs.WMSInvoker;
079    import org.deegree.ogcwebservices.wpvs.configuration.AbstractDataSource;
080    import org.deegree.ogcwebservices.wpvs.configuration.WPVSConfiguration;
081    import org.deegree.ogcwebservices.wpvs.j3d.DefaultSurface;
082    import org.deegree.ogcwebservices.wpvs.j3d.TerrainModel;
083    import org.deegree.ogcwebservices.wpvs.j3d.TexturedHeightMapTerrain;
084    import org.deegree.ogcwebservices.wpvs.j3d.TriangleTerrain;
085    import org.deegree.processing.raster.converter.Image2RawData;
086    import org.deegree.processing.raster.filter.Convolve;
087    import org.deegree.processing.raster.filter.RasterFilterException;
088    import org.j3d.geom.GeometryData;
089    
090    /**
091     * The <code>ResolutionStripe</code> class encapsulates a Surface with a maximum Resolution, which is convenient for the
092     * creation of a quadtree.
093     * 
094     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
095     * 
096     * @author last edited by: $Author: rbezema $
097     * 
098     * @version $Revision: 20595 $, $Date: 2009-11-05 13:26:40 +0100 (Do, 05 Nov 2009) $
099     * 
100     */
101    
102    public class ResolutionStripe implements Callable<ResolutionStripe>, Comparable<ResolutionStripe> {
103        private final static ILogger LOG = LoggerFactory.getLogger( ResolutionStripe.class );
104    
105        /**
106         * No information about the elevationmodel is known (e.g. no elevationModel is set)
107         */
108        public final static int ELEVATION_MODEL_UNKNOWN = 0;
109    
110        /**
111         * The elevationmodel uses a grid data serving datasource (e.g. CSW)
112         */
113        public final static int ELEVATION_MODEL_GRID = 1;
114    
115        /**
116         * The elevationmodel uses a point data serving datasource (e.g WFS)
117         */
118        public final static int ELEVATION_MODEL_POINTS = 2;
119    
120        private final double maxResolution;
121    
122        private final double minResolution;
123    
124        private Surface surface;
125    
126        private TerrainModel elevationModel = null;
127    
128        private AbstractDataSource elevationModelDataSource = null;
129    
130        private HashMap<String, BufferedImage> textures;
131    
132        private HashMap<String, OGCWebServiceException> textureExceptions;
133    
134        private ArrayList<AbstractDataSource> texturesDataSources;
135    
136        private HashMap<String, DefaultSurface> features;
137    
138        private ArrayList<AbstractDataSource> featureCollectionDataSources;
139    
140        private double minimalHeightlevel;
141    
142        private String outputFormat = null;
143    
144        private OrderedGroup resultingJ3DScene;
145    
146        private double scale;
147    
148        private int dgmType;
149    
150        private BufferedImage resultTexture = null;
151    
152        private List<Point3d> pointList;
153    
154        // a string for debugging containing the old resolution, if the resolution was fixed because of the maxRequestSize.
155        private String debugString = null;
156    
157        /**
158         * @param surface
159         *            of this resolution stripe, after the stripeFactory it is a trapezium, after the quad-splitter an
160         *            axis-alligned bbox, which will be a request.
161         * @param maximumResolution
162         *            the largest resolution value, resulting in the smallest map Resolution, e.g. farthest away from the
163         *            viewer.
164         * @param minimumResolution
165         *            the smallest resolution value, resulting in the highest map Resolution, e.g. nearest to the viewer.
166         * @param minimalHeight
167         *            the terrain height which will be taken if an error occurred while retrieving height data.
168         * @param scale
169         *            of the heights ( 1.0 means no scaling )
170         */
171        public ResolutionStripe( Surface surface, double maximumResolution, double minimumResolution, double minimalHeight,
172                                 double scale ) {
173            this.surface = surface;
174            // calculate the requestWidth and requestHeight.
175            int rH = (int) Math.round( surface.getEnvelope().getHeight() / Math.abs( minimumResolution ) );
176            int rW = (int) Math.round( surface.getEnvelope().getWidth() / Math.abs( minimumResolution ) );
177    
178            if ( rH > WPVSConfiguration.MAX_REQUEST_SIZE || rH < 0 || rW > WPVSConfiguration.MAX_REQUEST_SIZE || rW < 0 ) {
179                minResolution = ( ( rH > rW ) ? surface.getEnvelope().getHeight() : surface.getEnvelope().getWidth() )
180                                / WPVSConfiguration.MAX_REQUEST_SIZE;
181                maxResolution = minResolution;
182                debugString = "fixed resolution: " + minimumResolution;
183                LOG.logDebug( "Setting maxResolution to " + minResolution + " instead of " + minimumResolution
184                              + " because the request width or height would be larger as + "
185                              + WPVSConfiguration.MAX_REQUEST_SIZE + " pixel." );
186            } else {
187                this.minResolution = minimumResolution;
188                this.maxResolution = maximumResolution;
189            }
190            this.minimalHeightlevel = minimalHeight;
191            this.scale = scale;
192            featureCollectionDataSources = new ArrayList<AbstractDataSource>( 5 );
193            texturesDataSources = new ArrayList<AbstractDataSource>( 5 );
194            textures = new HashMap<String, BufferedImage>( 10 );
195            textureExceptions = new HashMap<String, OGCWebServiceException>( 10 );
196            features = new HashMap<String, DefaultSurface>( 1000 );
197            // resultingJ3DScene = new BranchGroup();
198            resultingJ3DScene = null;
199            dgmType = ResolutionStripe.ELEVATION_MODEL_UNKNOWN;
200        }
201    
202        /**
203         * @param surface
204         *            of this resolution stripe, after the stripeFactory it is a trapezium, after the quad-splitter an
205         *            axis-alligned bbox, which will be a request.
206         * @param maximumResolution
207         *            the largest resolution value, resulting in the smallest map Resolution, e.g. farthest away from the
208         *            viewer.
209         * @param minimumResolution
210         *            the smallest resolution value, resulting in the highest map Resolution, e.g. nearest to the viewer.
211         * @param minimalHeight
212         *            the terrain height which will be taken if an error occurred while retrieving height data.
213         * @param outputFormat
214         *            of the requests.
215         * @param scale
216         *            of the heights ( 1.0 means no scaling )
217         */
218        public ResolutionStripe( Surface surface, double maximumResolution, double minimumResolution, double minimalHeight,
219                                 String outputFormat, double scale ) {
220            this( surface, maximumResolution, minimumResolution, minimalHeight, scale );
221            this.outputFormat = outputFormat;
222        }
223    
224        /**
225         * @return the CRS of this ResolutionStripe
226         */
227        public CoordinateSystem getCRSName() {
228            return surface.getCoordinateSystem();
229        }
230    
231        /**
232         * @return the largest resolution value, resulting in the smallest map Resolution, e.g. farthest away from the
233         *         viewer.
234         */
235        public double getMaxResolution() {
236            return maxResolution;
237        }
238    
239        /**
240         * @return the (always possitive) resolution of the largest (away from the viewer) side of the surface as scale
241         *         denominator, which means divide by {@link MapUtils#DEFAULT_PIXEL_SIZE}.
242         */
243        public double getMaxResolutionAsScaleDenominator() {
244            return Math.abs( maxResolution ) / MapUtils.DEFAULT_PIXEL_SIZE;
245        }
246    
247        /**
248         * @return the (always possitive) resolution of the smallest (towards the viewer) side of the surface as scale
249         *         denominator, which means divide by {@link MapUtils#DEFAULT_PIXEL_SIZE}.
250         */
251        public double getMinResolutionAsScaleDenominator() {
252            return Math.abs( minResolution ) / MapUtils.DEFAULT_PIXEL_SIZE;
253        }
254    
255        /**
256         * @return the smallest resolution value, resulting in the highest map Resolution, e.g. nearest to the viewer.
257         */
258        public double getMinResolution() {
259            return minResolution;
260        }
261    
262        /**
263         * @return the geometric surface which is defines this resolutionStripe.
264         */
265        public Surface getSurface() {
266            return surface;
267        }
268    
269        /**
270         * @return the minimalTerrainHeight.
271         */
272        public double getMinimalTerrainHeight() {
273            return minimalHeightlevel;
274        }
275    
276        /**
277         * @return the requestwidth (in pixels) for the bbox containing this resolutionsstripe or -1 if the resolution is to
278         *         high (e.g. int overload)
279         */
280        public int getRequestWidthForBBox() {
281            int result = (int) Math.round( surface.getEnvelope().getWidth() / Math.abs( minResolution ) );
282            if ( result > 8000 ) {
283                LOG.logDebug( "Returning -1 for the requestImageWidth, the maxResolution: " + maxResolution
284                              + " the env.getHeight(): " + surface.getEnvelope().getHeight() );
285                return -1;
286            }
287            return result;
288        }
289    
290        /**
291         * @return the height (in pixels) of the request envelope or -1 if the resolution is to high
292         */
293        public int getRequestHeightForBBox() {
294            int result = (int) Math.round( surface.getEnvelope().getHeight() / Math.abs( minResolution ) );
295            if ( result > 80000 ) {
296                LOG.logDebug( "Returning -1 for the requestImageHeight, the maxResolution: " + maxResolution
297                              + " the env.getHeight(): " + surface.getEnvelope().getHeight() );
298                return -1;
299            }
300            return result;
301        }
302    
303        /**
304         * @return the elevationModel if no elevationModel is created jet, an {@link TriangleTerrain} elevation model of the
305         *         bbox of this stripe (with heightvalues set to minimalheigt) is returned.
306         */
307        public TerrainModel getElevationModel() {
308            if ( elevationModel == null ) {
309                elevationModel = createTriangleTerrainFromBBox();
310            }
311            return elevationModel;
312        }
313    
314        /**
315         * @param elevationModel
316         *            An other elevationModel.
317         */
318        public void setElevationModel( TerrainModel elevationModel ) {
319            this.elevationModel = elevationModel;
320        }
321    
322        /**
323         * @param pointList
324         *            containing Points which represents the heights of measures points (normally aquired from a wfs).
325         */
326        public void setElevationModelFromMeassurePoints( List<Point3d> pointList ) {
327            // LOG.logDebug( "Found following meassure points from a wfs: " + pointList );
328            this.pointList = pointList;
329            // A little hack.
330            // add the heightvalues of the nearest meassurepoint to the corner points of the bbox.
331            Point3d ll = new Point3d( Double.MAX_VALUE, Double.MAX_VALUE, 0 );
332            Point3d lr = new Point3d( Double.MIN_VALUE, Double.MAX_VALUE, 0 );
333            Point3d ur = new Point3d( Double.MIN_VALUE, Double.MIN_VALUE, 0 );
334            Point3d ul = new Point3d( Double.MAX_VALUE, Double.MIN_VALUE, 0 );
335            for ( Point3d p : pointList ) {
336                if ( p.x < ll.x && p.y < ll.y ) {
337                    ll.x = p.x;
338                    ll.y = p.y;
339                    ll.z = p.z;
340                }
341                if ( p.x > lr.x && p.y < lr.y ) {
342                    lr.x = p.x;
343                    lr.y = p.y;
344                    lr.z = p.z;
345                }
346                if ( p.x > ur.x && p.y > ur.y ) {
347                    ur.x = p.x;
348                    ur.y = p.y;
349                    ur.z = p.z;
350                }
351                if ( p.x < ul.x && p.y > ul.y ) {
352                    ul.x = p.x;
353                    ul.y = p.y;
354                    ul.z = p.z;
355                }
356            }
357            Position min = surface.getEnvelope().getMin();
358            Position max = surface.getEnvelope().getMax();
359            this.pointList.add( new Point3d( min.getX(), min.getY(), ll.z ) );
360            this.pointList.add( new Point3d( max.getX(), min.getY(), lr.z ) );
361            this.pointList.add( new Point3d( max.getX(), max.getY(), ur.z ) );
362            this.pointList.add( new Point3d( min.getX(), max.getY(), ul.z ) );
363        }
364    
365        /**
366         * @param heightMap
367         *            a BufferedImage which contains height values, normally aquired from a wcs.
368         */
369        public void setElevationModelFromHeightMap( BufferedImage heightMap ) {
370            Image2RawData i2rd = new Image2RawData( heightMap, (float) scale );
371            float[][] heights = i2rd.parse();
372    
373            // smooth the outcome of the rasterdata, by applying a lowpass filter.
374            float[][] kernel = new float[5][5];
375            for ( int i = 0; i < kernel.length; i++ ) {
376                for ( int j = 0; j < kernel[i].length; j++ ) {
377                    kernel[i][j] = 1;
378                }
379            }
380    
381            try {
382                heights = Convolve.perform( heights, kernel );
383            } catch ( RasterFilterException e ) {
384                e.printStackTrace();
385            }
386    
387            Envelope env = surface.getEnvelope();
388    
389            Position lowerLeft = surface.getEnvelope().getMin();
390            Vector3f lLeft = new Vector3f( (float) lowerLeft.getX(), (float) lowerLeft.getY(), 0 );
391    
392            // Triangles won't work -> an error in org.j3d.geom.terrain.ElevationGridGenerator therefor
393            // using TRIANGLE_STRIPS
394            LOG.logDebug( "Trying to create elevationmodel from the points received from a wcs" );
395            elevationModel = new TexturedHeightMapTerrain( (float) env.getWidth(), (float) env.getHeight(), heights, lLeft,
396                                                           GeometryData.TRIANGLE_STRIPS, false );
397            LOG.logDebug( "From the point list of the wcs created following elevationModel: " + elevationModel );
398        }
399    
400        /**
401         * @return the features of this resolutionstripe
402         */
403        public HashMap<String, DefaultSurface> getFeatures() {
404            return features;
405        }
406    
407        /**
408         * @param key
409         *            the name of the feature to be added.
410         * @param feature
411         *            (e.g a building, tree etc.) as a DefaultSurface (derived frome Shape3D) to be added to the hashmap.
412         * @return true if the feature wasn't allready defined in the hashmap and could therefore be inserted, or if the key
413         *         or feature are null.
414         */
415        public boolean addFeature( String key, DefaultSurface feature ) {
416            if ( feature != null && key != null ) {
417                DefaultSurface tmp = features.get( key );
418                if ( tmp == null && !features.containsKey( key ) ) {
419                    features.put( key, feature );
420                    return true;
421                }
422            }
423            return false;
424        }
425    
426        /**
427         * @return the textures value.
428         */
429        public HashMap<String, BufferedImage> getTextures() {
430            return textures;
431        }
432    
433        /**
434         * @param key
435         *            the name of the texture to be added.
436         * @param texture
437         *            to be added to the hashmap.
438         * @return true if the texture wasn't allready defined in the hashmap and could therefore be inserted, or if the key
439         *         or texture are null.
440         */
441        public boolean addTexture( String key, BufferedImage texture ) {
442            if ( texture != null && key != null ) {
443                BufferedImage tmp = textures.get( key );
444                if ( tmp == null && !textures.containsKey( key ) ) {
445                    textures.put( key, texture );
446                    return true;
447                }
448            }
449            return false;
450        }
451    
452        /**
453         * @return the elevationModelDataSource value.
454         */
455        public AbstractDataSource getElevationModelDataSource() {
456            return elevationModelDataSource;
457        }
458    
459        /**
460         * @param elevationModelDataSource
461         *            An other elevationModelDataSource value.
462         */
463        public void setElevationModelDataSource( AbstractDataSource elevationModelDataSource ) {
464            this.elevationModelDataSource = elevationModelDataSource;
465            if ( elevationModelDataSource.getServiceType() == AbstractDataSource.LOCAL_WFS
466                 || elevationModelDataSource.getServiceType() == AbstractDataSource.REMOTE_WFS ) {
467                dgmType = ResolutionStripe.ELEVATION_MODEL_POINTS;
468            } else if ( elevationModelDataSource.getServiceType() == AbstractDataSource.LOCAL_WCS
469                        || elevationModelDataSource.getServiceType() == AbstractDataSource.REMOTE_WCS ) {
470                dgmType = ResolutionStripe.ELEVATION_MODEL_GRID;
471            }
472        }
473    
474        /**
475         * @return the featureCollectionDataSources value.
476         */
477        public ArrayList<AbstractDataSource> getFeatureCollectionDataSources() {
478            return featureCollectionDataSources;
479        }
480    
481        /**
482         * @param featureCollectionDataSource
483         *            a DataSources for a specific featureCollection.
484         */
485        public void addFeatureCollectionDataSource( AbstractDataSource featureCollectionDataSource ) {
486            if ( featureCollectionDataSource != null ) {
487                if ( !featureCollectionDataSources.contains( featureCollectionDataSource ) ) {
488                    featureCollectionDataSources.add( featureCollectionDataSource );
489                }
490            }
491        }
492    
493        /**
494         * @return the texturesDataSources value.
495         */
496        public ArrayList<AbstractDataSource> getTexturesDataSources() {
497            return texturesDataSources;
498        }
499    
500        /**
501         * @param textureDataSource
502         *            An other texturesDataSources value.
503         */
504        public void addTextureDataSource( AbstractDataSource textureDataSource ) {
505            if ( textureDataSource != null ) {
506                if ( !texturesDataSources.contains( textureDataSource ) ) {
507                    texturesDataSources.add( textureDataSource );
508                }
509            }
510        }
511    
512        /**
513         * 
514         * @return the OutputFormat of the resultImage
515         */
516        public String getOutputFormat() {
517            return outputFormat;
518        }
519    
520        /**
521         * @param outputFormat
522         *            the mime type of the resultimage
523         */
524        public void setOutputFormat( String outputFormat ) {
525            this.outputFormat = outputFormat;
526        }
527    
528        /**
529         * After a call to this class call method, it is possible to get a Java3D representation --in form of a
530         * BranchGroup-- of this resolutionStripe. In this BranchGroup all the textures and requested features are added to
531         * the ElevationModel.
532         * 
533         * @return a Java3D representation of this ResolutionStripe.
534         */
535        public OrderedGroup getJava3DRepresentation() {
536            if ( resultingJ3DScene == null ) {
537                createJava3DRepresentation();
538            }
539            return resultingJ3DScene;
540        }
541    
542        @Override
543        public String toString() {
544            StringBuilder sb = new StringBuilder( 512 );
545            sb.append( "Resolution: " ).append( maxResolution ).append( "\n" );
546    
547            try {
548                sb.append( "Surface: " ).append( WKTAdapter.export( this.surface ) ).append( "\n" );
549            } catch ( GeometryException e ) {
550                e.printStackTrace();
551            }
552            sb.append( "FeatureCollectionDataSources:\n " );
553            if ( featureCollectionDataSources.size() == 0 ) {
554                sb.append( " - No feature collection datasources defined.\n" );
555            } else {
556                for ( int i = 0; i < featureCollectionDataSources.size(); ++i ) {
557                    sb.append( " - " ).append( i ).append( ") " ).append( featureCollectionDataSources.get( i ) ).append(
558                                                                                                                          "\n" );
559                }
560            }
561            sb.append( "TexturesDataSources:\n" );
562            if ( texturesDataSources.size() == 0 ) {
563                sb.append( " - No texture datasources defined.\n" );
564            } else {
565                for ( int i = 0; i < texturesDataSources.size(); ++i ) {
566                    sb.append( " - " ).append( i ).append( ") " ).append( texturesDataSources.get( i ) ).append( "\n" );
567                }
568            }
569    
570            sb.append( "ElevationDataSource: \n" );
571            if ( elevationModelDataSource == null ) {
572                sb.append( " - No elevation model datasources defined.\n" );
573            } else {
574                sb.append( " - " ).append( elevationModelDataSource ).append( "\n" );
575            }
576            return sb.toString();
577        }
578    
579        /**
580         * @return a well known representation of the geometry of this Resolutionstripe
581         */
582        public String toWKT() {
583            try {
584                return new StringBuffer( WKTAdapter.export( this.surface ) ).toString();
585            } catch ( GeometryException e ) {
586                e.printStackTrace();
587                return new String( "" );
588            }
589        }
590    
591        /**
592         * Outputs the textures to the tmp directory with following format:
593         * <code>key_response:___res:_maxresolution__random_id.jpg</code> this file will be deleted at jvm termination.
594         */
595        public void outputTextures() {
596    
597            Set<String> keys = textures.keySet();
598            Random rand = new Random( System.currentTimeMillis() );
599            for ( String key : keys ) {
600                try {
601                    // System.out.println( "saving image" );
602    
603                    File f = new File( key + "_response_" + rand.nextInt() + "__res_" + maxResolution + "___.jpg" );
604                    f.deleteOnExit();
605                    LOG.logDebug( "Saving result texture ( in file: " + f.getAbsolutePath() + " for resolution stripe\n"
606                                  + this );
607                    // System.out.println( f );
608                    // ImageUtils.saveImage( responseImage, f, 1 );
609                    ImageIO.write( textures.get( key ), "jpg", f );
610                } catch ( IOException e ) {
611                    e.printStackTrace();
612                } catch ( Exception e ) {
613                    e.printStackTrace();
614                }
615            }
616        }
617    
618        /**
619         * @return the scale of the heights (1.0 means no scaling)
620         */
621        public double getScale() {
622            return scale;
623        }
624    
625        /**
626         * @return one of the possible values {@link ResolutionStripe#ELEVATION_MODEL_UNKNOWN}
627         *         {@link ResolutionStripe#ELEVATION_MODEL_GRID} {@link ResolutionStripe#ELEVATION_MODEL_POINTS}
628         */
629        public int getDGMType() {
630            return dgmType;
631        }
632    
633        /**
634         * @return the resultTexture, which is build from all requested (wms/wcs) textures.
635         */
636        public BufferedImage getResultTexture() {
637            return resultTexture;
638        }
639    
640        /**
641         * This call method is part of the Deegree Concurrent framework ({@link org.deegree.framework.concurrent.Executor})
642         * . In this case it requests all the Data for a <code>ResolutionStripe</code> by invoking the necessary
643         * webservices.
644         * 
645         * @see java.util.concurrent.Callable#call()
646         */
647        public ResolutionStripe call()
648                                throws OGCWebServiceException {
649            int invokeCounter = 0;
650            // Strictly the different datasources must not be separated into two different
651            // DataSourceList, it might be handy (for caching) to do so though.
652            for ( AbstractDataSource textureDS : texturesDataSources ) {
653                invokeDataSource( textureDS, invokeCounter++ );
654            }
655            // create one texture from all textures
656            createTexture();
657    
658            // Create the buildings etc.
659            for ( AbstractDataSource featureDS : featureCollectionDataSources ) {
660                invokeDataSource( featureDS, invokeCounter++ );
661            }
662    
663            // create the terrain, if no terrain was requested, just create a flat square.
664            if ( elevationModelDataSource != null ) {
665                LOG.logDebug( "Invoking terrain datasource, because an elevation model was given." );
666                invokeDataSource( elevationModelDataSource, -1 );
667            } else {
668                LOG.logDebug( "Create flat triangle terrain, because no elevation model was given." );
669                elevationModel = createTriangleTerrainFromBBox();
670            }
671            // let this thread create the model in advance.
672            // if ( dgmType == ELEVATION_MODEL_GRID || dgmType == ELEVATION_MODEL_UNKNOWN) {
673            createJava3DRepresentation();
674            // }
675            return this;
676        }
677    
678        private void invokeDataSource( AbstractDataSource ads, int id ) {
679            try {
680                GetViewServiceInvoker invoker = null;
681                if ( ads.getServiceType() == AbstractDataSource.LOCAL_WMS
682                     || ads.getServiceType() == AbstractDataSource.REMOTE_WMS ) {
683                    invoker = new WMSInvoker( this, id );
684                } else if ( ads.getServiceType() == AbstractDataSource.LOCAL_WCS
685                            || ads.getServiceType() == AbstractDataSource.REMOTE_WCS ) {
686                    invoker = new WCSInvoker( this, id, outputFormat, ( ads == elevationModelDataSource ) );
687                } else { // WFS -> was checked in DefaultGetViewHandler
688                    invoker = new WFSInvoker( this, id, ( ads == elevationModelDataSource ) );
689                }
690                invoker.invokeService( ads );
691            } catch ( Throwable e ) {
692                if ( !Thread.currentThread().isInterrupted() ) {
693                    LOG.logError( "WPVS: error while invoking a datasource: " + e.getMessage(), e );
694                }
695            }
696        }
697    
698        private TriangleTerrain createTriangleTerrainFromBBox() {
699            List<Point3d> measurePoints = new ArrayList<Point3d>( 0 );
700            return new TriangleTerrain( measurePoints, surface.getEnvelope(), minimalHeightlevel, scale );
701        }
702    
703        /**
704         * Creates a java3d representation of the data wihtin this resolutionstripe. If the DGM is defined from
705         * measurepoints, these points are triangualuated before createing the OrderedGroup.
706         */
707        private void createJava3DRepresentation() {
708            resultingJ3DScene = new OrderedGroup();
709    
710            if ( resultTexture != null && elevationModel != null ) {
711                LOG.logDebug( "Creating an elevationmodel for the resolutionstripe" );
712                elevationModel.setTexture( resultTexture );
713                elevationModel.createTerrain();
714                resultingJ3DScene.addChild( elevationModel );
715            }
716    
717            // add the features (e.g. buildings) to the j3d representation of this resolutionstripe.
718            Collection<DefaultSurface> featureSurfaces = features.values();
719            if ( featureSurfaces != null ) {
720                for ( DefaultSurface ds : featureSurfaces ) {
721                    resultingJ3DScene.addChild( ds );
722                }
723            }
724            // if( LOG.getLevel() == ILogger.LOG_DEBUG ){
725            //
726            //
727            // try {
728            // File f = File.createTempFile( "features", ".xml" );
729            // J3DToCityGMLExporter exporter = new J3DToCityGMLExporter( "test", surface.getCoordinateSystem().getName(),
730            // "bla", f.getParent()+File.separator+"textures", 0, 0, 0, false );
731            // StringBuilder sb = new StringBuilder( 200000 );
732            // exporter.export( sb, resultingJ3DScene );
733            // LOG.logDebug( "Writing citygml file to: " + f.getAbsoluteFile() );
734            // BufferedWriter out = new BufferedWriter( new FileWriter( f ) );
735            // out.write( sb.toString() );
736            // out.flush();
737            // out.close();
738            // } catch ( IOException e ) {
739            // // TODO Auto-generated catch block
740            // e.printStackTrace();
741            // }
742            // }
743        }
744    
745        /**
746         * Paints all textures on the given bufferedImage
747         */
748        private void createTexture() {
749            Collection<BufferedImage> textureImages = textures.values();
750            Graphics2D g2d = null;
751            if ( textureImages != null && !textureImages.isEmpty() ) {
752                // create texture as BufferedImage
753                if ( textureImages.size() > 0 ) {
754                    Iterator<BufferedImage> it = textureImages.iterator();
755                    resultTexture = it.next();
756                    // get the g2d from the resulttexture in advance.
757                    if ( resultTexture != null ) {
758                        g2d = (Graphics2D) resultTexture.getGraphics();
759                    }
760                    while ( it.hasNext() ) {
761                        if ( resultTexture == null ) {
762                            resultTexture = it.next();
763                        } else {
764                            if ( g2d == null ) {
765                                g2d = (Graphics2D) resultTexture.getGraphics();
766                            }
767                            // draw the next image on the g2d
768                            g2d.drawImage( it.next(), 0, 0, null );
769                        }
770                    }
771                }
772            }
773            if ( resultTexture == null ) {
774                // no images were found, so output the error messages or the default error message.
775                LOG.logDebug( "No images were found (or all were null), therefore outputing the errormessages" );
776                resultTexture = new BufferedImage( getRequestWidthForBBox(), getRequestHeightForBBox(),
777                                                   BufferedImage.TYPE_INT_ARGB );
778                g2d = (Graphics2D) resultTexture.getGraphics();
779    
780                if ( texturesDataSources.size() > 0 ) {
781                    Collection<OGCWebServiceException> exceptions = textureExceptions.values();
782                    String[] exceptionStrings = new String[exceptions.size()];
783                    int count = 0;
784                    for ( OGCWebServiceException ogcwse : exceptions ) {
785    
786                        String message = StringTools.concat( 100, "error (", Integer.valueOf( count + 1 ), "): ",
787                                                             ogcwse.getMessage() );
788                        exceptionStrings[count++] = message;
789                    }
790                    paintString( g2d, exceptionStrings );
791                } else {
792                    g2d.setColor( Color.WHITE );
793                    g2d.drawRect( 0, 0, resultTexture.getWidth(), resultTexture.getHeight() );
794                }
795            }
796            if ( g2d != null ) {
797    
798                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
799                    Stroke s = g2d.getStroke();
800                    Color c = g2d.getColor();
801                    g2d.setColor( Color.RED );
802                    g2d.setStroke( new BasicStroke( 3 ) );
803                    g2d.drawRect( 2, 2, resultTexture.getWidth() - 2, resultTexture.getHeight() - 2 );
804                    g2d.setColor( c );
805                    g2d.setStroke( s );
806    
807                    paintString( g2d, new String[] { Double.toString( minResolution ),
808                                                    ( ( debugString != null ) ? debugString : "" ),
809                                                    Double.toString( maxResolution ) } );
810                }
811                g2d.dispose();
812            }
813    
814        }
815    
816        private void paintString( Graphics2D g2d, String[] stringsToPaint ) {
817            Font originalFont = g2d.getFont();
818            float originalFontSize = originalFont.getSize();
819            if ( stringsToPaint == null || stringsToPaint.length == 0 ) {
820                LOG.logError( Messages.getMessage( "WPVS_NO_STRINGS" ) );
821                return;
822            }
823            // find the largest string
824            String testString = new String();
825            for ( int i = 0; i < stringsToPaint.length; ++i ) {
826                if ( stringsToPaint[i].length() > testString.length() ) {
827                    testString = stringsToPaint[i];
828                }
829            }
830            // calculate the maximal height (with respect to the strings) of the font.
831            float requestWidth = getRequestWidthForBBox();
832            float requestHeight = getRequestHeightForBBox();
833            float maxFontHeight = requestHeight / stringsToPaint.length;
834            TextLayout tl = new TextLayout( testString, originalFont, g2d.getFontRenderContext() );
835            Rectangle2D r2d = tl.getBounds();
836            float width = (float) r2d.getWidth();
837            float height = (float) r2d.getHeight();
838    
839            // little widther than the requestwidth ensures total readabillity
840            float approx = requestWidth / ( width * 1.2f );
841            if ( ( originalFontSize * approx ) < maxFontHeight ) {
842                originalFont = originalFont.deriveFont( originalFontSize * approx );
843            } else {
844                originalFont = originalFont.deriveFont( maxFontHeight );
845            }
846            tl = new TextLayout( testString, originalFont, g2d.getFontRenderContext() );
847            r2d = tl.getBounds();
848            width = (float) r2d.getWidth();
849            height = (float) r2d.getHeight();
850    
851            int x = (int) Math.round( ( requestWidth * 0.5 ) - ( width * 0.5 ) );
852            int stringOffset = (int) Math.round( ( requestHeight * ( 1d / ( stringsToPaint.length + 1 ) ) )
853                                                 + ( height * 0.5 ) );
854            g2d.setColor( Color.GRAY );
855            // g2d.drawRect( 0, 0, (int) requestWidth, (int) requestHeight );
856            g2d.setColor( Color.RED );
857            g2d.setFont( originalFont );
858            for ( int i = 0; i < stringsToPaint.length; ++i ) {
859                int y = ( i + 1 ) * stringOffset;
860                g2d.drawString( stringsToPaint[i], x, y );
861            }
862        }
863    
864        /**
865         * @param datasourceID
866         *            the id of the datasources which received an exception while retrieving a texture.
867         * @param exception
868         *            the exception that occured while invoking the datasource.
869         */
870        public void setTextureRetrievalException( String datasourceID, OGCWebServiceException exception ) {
871            if ( datasourceID != null && exception != null ) {
872                if ( !textureExceptions.containsKey( datasourceID ) ) {
873                    textureExceptions.put( datasourceID, exception );
874                }
875            }
876        }
877    
878        /**
879         * @return the pointList
880         */
881        public final List<Point3d> getMeassurepointsAsList() {
882            return pointList;
883        }
884    
885        /*
886         * (non-Javadoc)
887         * 
888         * @see java.lang.Comparable#compareTo(java.lang.Object)
889         */
890        public int compareTo( ResolutionStripe other ) {
891            if ( Math.abs( other.maxResolution - this.maxResolution ) < 0.0001 ) {
892                return 0;
893            }
894            return ( ( this.maxResolution - other.maxResolution ) < 0 ) ? -1 : 1;
895    
896        }
897    
898    }