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