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