    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }