001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcwebservices/wpvs/DefaultGetViewHandler.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;
038    
039    import java.awt.Color;
040    import java.awt.Font;
041    import java.awt.Graphics;
042    import java.awt.Graphics2D;
043    import java.awt.RenderingHints;
044    import java.awt.geom.AffineTransform;
045    import java.awt.image.BufferedImage;
046    import java.io.File;
047    import java.io.IOException;
048    import java.net.URL;
049    import java.util.ArrayList;
050    import java.util.Calendar;
051    import java.util.List;
052    import java.util.concurrent.Callable;
053    import java.util.concurrent.CancellationException;
054    
055    import javax.imageio.ImageIO;
056    import javax.media.j3d.Background;
057    import javax.media.j3d.BoundingSphere;
058    import javax.media.j3d.Canvas3D;
059    import javax.media.j3d.ImageComponent2D;
060    import javax.media.j3d.Node;
061    import javax.media.j3d.OrderedGroup;
062    import javax.vecmath.Color3f;
063    import javax.vecmath.Point3d;
064    
065    import org.deegree.framework.concurrent.ExecutionFinishedEvent;
066    import org.deegree.framework.concurrent.Executor;
067    import org.deegree.framework.log.ILogger;
068    import org.deegree.framework.log.LoggerFactory;
069    import org.deegree.framework.util.StringTools;
070    import org.deegree.framework.util.TimeTools;
071    import org.deegree.i18n.Messages;
072    import org.deegree.model.crs.CRSFactory;
073    import org.deegree.model.crs.CRSTransformationException;
074    import org.deegree.model.crs.CoordinateSystem;
075    import org.deegree.model.crs.GeoTransformer;
076    import org.deegree.model.crs.UnknownCRSException;
077    import org.deegree.model.spatialschema.Envelope;
078    import org.deegree.model.spatialschema.GeometryException;
079    import org.deegree.model.spatialschema.Surface;
080    import org.deegree.model.spatialschema.WKTAdapter;
081    import org.deegree.ogcbase.ExceptionCode;
082    import org.deegree.ogcwebservices.InconsistentRequestException;
083    import org.deegree.ogcwebservices.OGCWebServiceException;
084    import org.deegree.ogcwebservices.wpvs.capabilities.Dataset;
085    import org.deegree.ogcwebservices.wpvs.capabilities.ElevationModel;
086    import org.deegree.ogcwebservices.wpvs.configuration.AbstractDataSource;
087    import org.deegree.ogcwebservices.wpvs.configuration.WPVSConfiguration;
088    import org.deegree.ogcwebservices.wpvs.configuration.WPVSDeegreeParams;
089    import org.deegree.ogcwebservices.wpvs.j3d.OffScreenWPVSRenderer;
090    import org.deegree.ogcwebservices.wpvs.j3d.TriangleTerrain;
091    import org.deegree.ogcwebservices.wpvs.j3d.ViewPoint;
092    import org.deegree.ogcwebservices.wpvs.j3d.WPVSScene;
093    import org.deegree.ogcwebservices.wpvs.operation.GetView;
094    import org.deegree.ogcwebservices.wpvs.operation.GetViewResponse;
095    import org.deegree.ogcwebservices.wpvs.utils.QuadTreeSplitter;
096    import org.deegree.ogcwebservices.wpvs.utils.ResolutionStripe;
097    import org.deegree.ogcwebservices.wpvs.utils.StripeFactory;
098    
099    import com.sun.j3d.utils.image.TextureLoader;
100    
101    /**
102     * Default handler for WPVS GetView requests. This Class is the central position where the {@link GetView} request lands
103     * the configured Datasources are gathered and the scene is put together.
104     * 
105     * @author <a href="mailto:taddei@lat-lon.de">Ugo Taddei</a>
106     * @author last edited by: $Author: rbezema $
107     * 
108     *         $Revision: 19665 $, $Date: 2009-09-16 10:11:29 +0200 (Mi, 16. Sep 2009) $
109     * 
110     */
111    public class DefaultGetViewHandler extends GetViewHandler {
112    
113        static private final ILogger LOG = LoggerFactory.getLogger( DefaultGetViewHandler.class );
114    
115        private WPVSConfiguration config;
116    
117        private URL backgroundImgURL;
118    
119        private WPVSScene theScene;
120    
121        private OffScreenWPVSRenderer renderer;
122    
123        /**
124         * Constructor for DefaultGetViewHandler.
125         * 
126         * @param owner
127         *            the service creating this handler
128         */
129        public DefaultGetViewHandler( WPVService owner ) {
130            super( owner );
131            this.config = owner.getConfiguration();
132        }
133    
134        /**
135         * This Method handles a clients GetView request by creating the appropriate (configured) Datasources, the
136         * {@link ResolutionStripe}s, the requeststripes (which are actually axisalligned Resolutionsripes) and finally
137         * putting them all together in a java3d scene. The creation of the Shape3D Objects (by requesting the
138         * ResolutionStripe relevant Datasources) is done in separate Threads for each ResolutionStripe (which is in
139         * conflict with the deegree styleguides) using the {@link Executor} class.
140         * 
141         * @see org.deegree.ogcwebservices.wpvs.GetViewHandler#handleRequest(org.deegree.ogcwebservices.wpvs.operation.GetView)
142         */
143        @Override
144        public GetViewResponse handleRequest( final GetView request )
145                                throws OGCWebServiceException {
146    
147            // request = req;
148            long totalTime = System.currentTimeMillis();
149            validateImageSize( request );
150    
151            List<Dataset> validDatasets = new ArrayList<Dataset>();
152            String errorMessage = getValidRequestDatasets( request, validDatasets );
153            if ( validDatasets.size() == 0 ) {
154                throw new OGCWebServiceException(
155                                                  StringTools.concat(
156                                                                      errorMessage.length() + 100,
157                                                                      "Your request yields no results for given reasons:\n",
158                                                                      errorMessage ) );
159    
160            }
161    
162            ElevationModel elevationModel = getValidElevationModel( request.getElevationModel() );
163            LOG.logDebug( "Requested elevationModel: " + elevationModel );
164    
165            if ( request.getFarClippingPlane() > config.getDeegreeParams().getMaximumFarClippingPlane() ) {
166                request.setFarClippingPlane( config.getDeegreeParams().getMaximumFarClippingPlane() );
167            }
168    
169            ViewPoint viewPoint = new ViewPoint( request, getTerrainHeightAboveSeaLevel( request.getPointOfInterest() ) );
170            LOG.logDebug( "Viewpoint: " + viewPoint );
171    
172            ArrayList<ResolutionStripe> resolutionStripes = createRequestBoxes( request, viewPoint,
173                                                                                config.getSmallestMinimalScaleDenomiator() );
174            LOG.logDebug( "Found number of resolutionStripes: " + resolutionStripes.size() );
175    
176            if ( resolutionStripes.size() == 0 ) {
177                throw new OGCWebServiceException(
178                                                  StringTools.concat( 200,
179                                                                      "There were no RequestBoxes found, therefor this WPVS-request is invalid" ) );
180    
181            }
182    
183            Surface visibleArea = viewPoint.getVisibleArea();
184    
185            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
186                try {
187                    LOG.logDebug( "Visible Area:\n" + WKTAdapter.export( visibleArea ).toString() );
188                } catch ( GeometryException e1 ) {
189                    // TODO Auto-generated catch block
190                    e1.printStackTrace();
191                }
192            }
193    
194            findValidDataSourcesFromDatasets( validDatasets, resolutionStripes, request.getOutputFormat(), visibleArea );
195            LOG.logDebug( "Using validDatasets: " + validDatasets );
196    
197            findValidEMDataSourceFromElevationModel( elevationModel, resolutionStripes, visibleArea );
198            LOG.logDebug( "Using elevationModel: " + elevationModel );
199    
200            // will also check is background is valid, before doing any hard work
201            this.backgroundImgURL = createBackgroundImageURL( request );
202    
203            LOG.logDebug( "Backgroundurl : " + backgroundImgURL );
204    
205            Executor exec = Executor.getInstance();
206            List<ExecutionFinishedEvent<ResolutionStripe>> resultingBoxes = null;
207            try {
208                LOG.logDebug( "Trying to synchronously contact datasources." );
209                resultingBoxes = exec.performSynchronously( new ArrayList<Callable<ResolutionStripe>>( resolutionStripes ),
210                                                            config.getDeegreeParams().getRequestTimeLimit() );
211            } catch ( InterruptedException ie ) {
212                throw new OGCWebServiceException(
213                                                  StringTools.concat( 200,
214                                                                      "A Threading-Error occurred while placing your request." ) );
215    
216            }
217    
218            LOG.logDebug( "All resolutionstripes finished executing." );
219    
220            if ( resultingBoxes != null && resultingBoxes.size() > 0 ) {
221                // check if some of the resolutionstripes finished execution before the configured max
222                // life time.
223                int k = 0;
224                for ( ExecutionFinishedEvent<ResolutionStripe> efe : resultingBoxes ) {
225                    try {
226                        efe.getResult();
227                    } catch ( CancellationException ce ) {
228                        k++;
229                    } catch ( Throwable e ) {
230                        // not interested in something else
231                    }
232                }
233                if ( k == resultingBoxes.size() ) {
234                    throw new OGCWebServiceException(
235                                                      Messages.getMessage(
236                                                                           "WPVS_EXCEEDED_REQUEST_TIME",
237                                                                           new Double(
238                                                                                       config.getDeegreeParams().getRequestTimeLimit() * 0.001 ) ).toString() );
239                }
240                double dataRetrievalTime = ( System.currentTimeMillis() - totalTime ) / 1000d;
241                long creationTime = System.currentTimeMillis();
242                theScene = createScene( request, viewPoint, resolutionStripes, visibleArea.getEnvelope() );
243                // Get a canvas3D from the pool.
244                Canvas3D canvas = WPVSConfiguration.getCanvas3D();
245                renderer = new OffScreenWPVSRenderer( canvas, config.getDeegreeParams().getNearClippingPlane(), theScene,
246                                                      request.getImageDimension().width,
247                                                      request.getImageDimension().height,
248                                                      config.getDeegreeParams().isAntialiasingEnabled() );
249                double creationT = ( ( System.currentTimeMillis() - creationTime ) / 1000d );
250                LOG.logDebug( "Trying to render Scene." );
251    
252                long renderTime = System.currentTimeMillis();
253                BufferedImage output = renderScene();
254                // Release the canvas so it may be reused by another request.
255                /**
256                 * If using the TestWPVS class option NO_NEW_REQUEST, make sure to comment following line as well as the
257                 * universe.cleanup(); and view.removeAllCanvas3Ds(); lines in the
258                 * {@link OffscreenWPVSRenderer#renderScene()}.
259                 */
260                WPVSConfiguration.releaseCanvas3D( canvas );
261                double renderT = ( ( System.currentTimeMillis() - renderTime ) / 1000d );
262    
263                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
264                    StringBuilder sb = new StringBuilder(
265                                                          "\n-----------TIMING INFORMATION----------\nThe handling of the request took: " );
266                    sb.append( ( System.currentTimeMillis() - totalTime ) / 1000d );
267                    sb.append( " seconds to return." );
268                    sb.append( "\n- Retreiving data took: " ).append( dataRetrievalTime ).append( "seconds." );
269                    sb.append( "\n- Creating j3d scene took: " ).append( creationT ).append( "seconds." );
270                    sb.append( "\n- Rendering the j3d scene took: " ).append( renderT ).append( "seconds." );
271                    sb.append( "\n--------------------------------------\n\n Outputting all retreived textures for " ).append(
272                                                                                                                               resolutionStripes.size() ).append(
273                                                                                                                                                                  " finished resolutionsSripes." );
274                    LOG.logDebug( sb.toString() );
275                    for ( ResolutionStripe stripe : resolutionStripes ) {
276                        stripe.outputTextures();
277                    }
278                    LOG.logDebug( "Number of resolutionStripes: " + resolutionStripes.size() );
279                }
280    
281                return new GetViewResponse( output, request.getOutputFormat() );
282            }
283            return null;
284        }
285    
286        /**
287         * @param elevationModelName
288         * @return an elevationModell or <code>null</code> if no name was given
289         * @throws OGCWebServiceException
290         *             if no elevationModel is found for the given elevationmodelname
291         */
292        private ElevationModel getValidElevationModel( String elevationModelName )
293                                throws OGCWebServiceException {
294            ElevationModel resultEMModel = null;
295            if ( elevationModelName != null ) {
296                resultEMModel = config.findElevationModel( elevationModelName );// dataset.getElevationModel();
297                if ( resultEMModel == null ) {
298                    throw new OGCWebServiceException( StringTools.concat( 150, "ElevationModel '", elevationModelName,
299                                                                          "' is not known to the WPVS" ) );
300                }
301            }
302            return resultEMModel;
303        }
304    
305        /**
306         * Finds the datasets which can handle the requested crs's, and check if they are defined inside the requested bbox
307         * 
308         * @param request
309         *            the GetView request
310         * @param resultDatasets
311         *            a list to which the found datasets will be added.
312         * @return an ArrayList containing all the datasets which comply with the requested crs.
313         * @throws OGCWebServiceException
314         */
315        private String getValidRequestDatasets( GetView request, List<Dataset> resultDatasets )
316                                throws OGCWebServiceException {
317            // ArrayList<Dataset> resultDatasets = new ArrayList<Dataset>();
318            Envelope bbox = request.getBoundingBox();
319            List<String> datasets = request.getDatasets();
320            CoordinateSystem coordSys = request.getCrs();
321    
322            // If the BoundingBox request not is
323            try {
324                if ( !"EPSG:4326".equalsIgnoreCase( coordSys.getFormattedString() ) ) {
325                    // transform the bounding box of the request to EPSG:4326/WGS 84
326                    GeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
327                    bbox = gt.transform( bbox, coordSys );
328                }
329            } catch ( CRSTransformationException e ) {
330                LOG.logError( e.getMessage(), e );
331                throw new OGCWebServiceException( e.getMessage() );
332            } catch ( UnknownCRSException e ) {
333                LOG.logError( e.getMessage(), e );
334                throw new OGCWebServiceException( e.getMessage() );
335            }
336            StringBuffer errorMessage = new StringBuffer( 1000 );
337            for ( String dset : datasets ) {
338                Dataset configuredDataset = config.findDataset( dset );
339                if ( configuredDataset != null ) {
340                    CoordinateSystem[] dataSetCRS = configuredDataset.getCrs();
341                    boolean isCoordSysSupported = false;
342                    boolean requestIntersectsWithDataset = false;
343                    for ( CoordinateSystem crs : dataSetCRS ) {
344                        if ( crs.equals( coordSys ) ) {
345                            isCoordSysSupported = true;
346                            // lookslike compatible crs therefor check if bbox intersect
347                            if ( configuredDataset.getWgs84BoundingBox().intersects( bbox ) ) {
348                                requestIntersectsWithDataset = true;
349                                if ( !resultDatasets.contains( configuredDataset ) ) {
350                                    resultDatasets.add( configuredDataset );
351                                }
352                            }
353                        }
354                    }
355                    if ( !isCoordSysSupported ) {
356                        String msg = new StringBuilder( "Requested Dataset -" ).append( dset ).append(
357                                                                                                       "- does not support requested crs: " ).append(
358                                                                                                                                                      coordSys ).append(
359                                                                                                                                                                         ".\n" ).toString();
360                        LOG.logDebug( msg );
361                        errorMessage.append( msg );
362                    } else if ( !requestIntersectsWithDataset ) {
363                        String msg = new StringBuilder( "Requested Dataset -" ).append( dset ).append(
364                                                                                                       "- does not intersect with the requested bbox.\n" ).toString();
365                        LOG.logDebug( msg );
366                        errorMessage.append( msg );
367                    }
368                } else {
369                    throw new InconsistentRequestException( Messages.getMessage( "WPVS_GETVIEW_INVALID_DATASET", dset ) );
370                }
371            }
372    
373            return errorMessage.toString();
374        }
375    
376        /**
377         * Finds the valid datasources in the configured (and allready checked) datasets.
378         * 
379         * @param datasets
380         *            the datasets which are valid for this request
381         */
382        private void findValidDataSourcesFromDatasets( List<Dataset> datasets, ArrayList<ResolutionStripe> stripes,
383                                                       String outputFormat, Surface visibleArea ) {
384            double resolution = 0;
385            for ( ResolutionStripe stripe : stripes ) {
386                stripe.setOutputFormat( outputFormat );
387                resolution = stripe.getMaxResolutionAsScaleDenominator();
388                for ( int i = 0; i < datasets.size(); ++i ) {
389                    Dataset dset = datasets.get( i );
390                    AbstractDataSource[] dataSources = dset.getDataSources();
391    
392                    for ( AbstractDataSource ads : dataSources ) {
393                        // System.out.println( "AbstractDataSource: " + ads );
394                        if ( resolution >= dset.getMinimumScaleDenominator()
395                             && resolution < dset.getMaximumScaleDenominator()
396                             && resolution >= ads.getMinScaleDenominator() && resolution < ads.getMaxScaleDenominator()
397                             && ( ( ads.getValidArea() != null ) ? ads.getValidArea().intersects( visibleArea ) : true ) ) {
398                            if ( ads.getServiceType() == AbstractDataSource.LOCAL_WFS
399                                 || ads.getServiceType() == AbstractDataSource.REMOTE_WFS ) {
400                                stripe.addFeatureCollectionDataSource( ads );
401                            } else if ( ads.getServiceType() == AbstractDataSource.LOCAL_WMS
402                                        || ads.getServiceType() == AbstractDataSource.REMOTE_WMS
403                                        || ads.getServiceType() == AbstractDataSource.LOCAL_WCS
404                                        || ads.getServiceType() == AbstractDataSource.REMOTE_WCS ) {
405                                stripe.addTextureDataSource( ads );
406                            }
407                        }
408                    }
409                }
410            }
411        }
412    
413        /**
414         * @param elevationModel
415         *            and it's datasources.
416         */
417        private void findValidEMDataSourceFromElevationModel( ElevationModel elevationModel,
418                                                              ArrayList<ResolutionStripe> stripes, Surface visibleArea ) {
419            if ( elevationModel != null ) {
420                AbstractDataSource[] emDataSources = elevationModel.getDataSources();
421                Dataset dataset = elevationModel.getParentDataset();
422                for ( ResolutionStripe stripe : stripes ) {
423                    double resolution = stripe.getMaxResolutionAsScaleDenominator();// getMaxResolution();
424                    for ( AbstractDataSource ads : emDataSources ) {
425                        if ( resolution >= dataset.getMinimumScaleDenominator()
426                             && resolution < dataset.getMaximumScaleDenominator()
427                             && resolution >= ads.getMinScaleDenominator() && resolution < ads.getMaxScaleDenominator()
428                             && ( ( ads.getValidArea() != null ) ? ads.getValidArea().intersects( visibleArea ) : true ) ) {
429                            stripe.setElevationModelDataSource( ads );
430                        }
431                    }
432                }
433            }
434        }
435    
436        /**
437         * Extracts from the request and the configuration the URL behind teh name of a given BACKGROUND
438         * 
439         * @return the URL, under which the background image is found
440         * @throws OGCWebServiceException
441         *             if no URL with the name given by 'BACKGROUND' can be found.
442         */
443        private URL createBackgroundImageURL( GetView request )
444                                throws OGCWebServiceException {
445    
446            String imageName = request.getVendorSpecificParameter( "BACKGROUND" );
447            URL imgURL = null;
448            if ( imageName != null ) {
449                imgURL = config.getDeegreeParams().getBackgroundMap().get( imageName );
450                if ( imgURL == null ) {
451                    String s = StringTools.concat( 100, "Cannot find any image referenced", "by parameter BACKGROUND=",
452                                                   imageName );
453                    throw new OGCWebServiceException( s );
454                }
455            }
456    
457            return imgURL;
458        }
459    
460        /**
461         * Creates a Java3D Node representing the background.
462         * 
463         * @param viewPoint
464         *            the viewersposition
465         * @return a new Node containing a geometry representing the background
466         * @throws OGCWebServiceException
467         */
468        private Node createBackground( ViewPoint viewPoint, GetView request )
469                                throws OGCWebServiceException {
470    
471            Point3d observer = viewPoint.getObserverPosition();
472    
473            Background bg = new Background( new Color3f( request.getBackgroundColor() ) );
474            BoundingSphere bounds = new BoundingSphere( observer,
475                                                        config.getDeegreeParams().getMaximumFarClippingPlane() * 1.1 );
476    
477            bg.setApplicationBounds( bounds );
478            try {
479                if ( backgroundImgURL != null ) {
480                    BufferedImage buffImg = ImageIO.read( backgroundImgURL );
481    
482                    // scale image to fill the whole background
483                    BufferedImage tmpImg = new BufferedImage( request.getImageDimension().width,
484                                                              request.getImageDimension().height, buffImg.getType() );
485                    Graphics g = tmpImg.getGraphics();
486                    g.drawImage( buffImg, 0, 0, tmpImg.getWidth() - 1, tmpImg.getHeight() - 1, null );
487                    g.dispose();
488    
489                    ImageComponent2D img = new TextureLoader( tmpImg ).getImage();
490                    bg.setImage( img );
491                }
492            } catch ( IOException e ) {
493                LOG.logError( e.getMessage(), e );
494                String s = StringTools.concat( 100, "Could not create backgound image: ", e.getMessage() );
495                throw new OGCWebServiceException( s );
496            }
497    
498            return bg;
499        }
500    
501        /**
502         * Creates the request boxes from the parameters available i teh incoming request.
503         * 
504         * @param viewPoint
505         *            where the viewer is
506         * @return a new array of surfaces representing the area in which data will be collected
507         */
508        private ArrayList<ResolutionStripe> createRequestBoxes( GetView request, ViewPoint viewPoint,
509                                                                double smallestMinimalScaleDenominator ) {
510            ArrayList<ResolutionStripe> requestStripes = new ArrayList<ResolutionStripe>();
511            String splittingMode = request.getVendorSpecificParameter( "SPLITTER" );
512            StripeFactory stripesFactory = new StripeFactory( viewPoint, smallestMinimalScaleDenominator );
513            int imageWidth = request.getImageDimension().width;
514            if ( "BBOX".equals( splittingMode ) ) {
515                requestStripes = stripesFactory.createBBoxResolutionStripe(
516                                                                            request.getBoundingBox(),
517                                                                            imageWidth,
518                                                                            getTerrainHeightAboveSeaLevel( viewPoint.getPointOfInterest() ),
519                                                                            request.getScale() );
520            } else {
521                // Calculate the resolution stripes perpendicular to the viewdirection
522                requestStripes = stripesFactory.createResolutionStripes(
523                                                                         request.getImageDimension().width,
524                                                                         getTerrainHeightAboveSeaLevel( viewPoint.getPointOfInterest() ),
525                                                                         null, request.getScale() );
526                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
527                    StringBuilder sb = new StringBuilder( "The requestStripes (in WKT) before the quadtree: \n" );
528                    for ( ResolutionStripe stripe : requestStripes ) {
529                        try {
530                            sb.append( WKTAdapter.export( stripe.getSurface() ) ).append( "\n" );
531                        } catch ( GeometryException e ) {
532                            LOG.logError( "Error while exporting surface to wkt.", e );
533                        }
534                    }
535                    LOG.logDebug( sb.toString() );
536                }
537                QuadTreeSplitter splittree = new QuadTreeSplitter( requestStripes, request.getImageDimension().width,
538                                                                   config.getDeegreeParams().isRequestQualityPreferred() );
539                requestStripes = splittree.getRequestQuads( config.getDeegreeParams().getExtendRequestPercentage(),
540                                                            config.getDeegreeParams().getQuadMergeCount() );
541                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
542                    StringBuilder sb = new StringBuilder( "The requestStripes (in WKT) after the quadtree: \n" );
543                    for ( ResolutionStripe stripe : requestStripes ) {
544                        try {
545                            sb.append( WKTAdapter.export( stripe.getSurface() ) ).append( "\n" );
546                        } catch ( GeometryException e ) {
547                            LOG.logError( "Error while exporting surface to wkt.", e );
548                        }
549                    }
550                    LOG.logDebug( sb.toString() );
551                }
552            }
553            return requestStripes;
554        }
555    
556        /**
557         * Should return the height of the terrain above the sealevel. TODO just returns a constant value, i.e. the
558         * configured MinimalTerrainHeight.
559         * 
560         * @param eyePositionX
561         *            to find the height of the sealevel for (not used yet)
562         * @param eyePositionY
563         *            to find the height of the sealevel for (not used yet)
564         * @param eyePositionZ
565         *            to find the height of the sealevel for (not used yet)
566         * @return the height above the seaLevel.
567         */
568        protected double getTerrainHeightAboveSeaLevel( double eyePositionX, double eyePositionY, double eyePositionZ ) {
569            return getTerrainHeightAboveSeaLevel( new Point3d( eyePositionX, eyePositionY, eyePositionZ ) );
570        }
571    
572        /**
573         * Should return the height of the terrain above the sealevel. TODO just returns a constant value, i.e. the
574         * configured MinimalTerrainHeight.
575         * 
576         * @param eyePosition
577         *            to find the height of the sealevel for (not used yet)
578         * 
579         * @return the height above the seaLevel.
580         */
581        protected double getTerrainHeightAboveSeaLevel( Point3d eyePosition ) {
582            return WPVSConfiguration.getHeightForPosition( eyePosition );
583        }
584    
585        /**
586         * Creates a WPVS scene
587         * 
588         * @param viewPoint
589         *            position of the viewer
590         * @return a new scene
591         * @throws OGCWebServiceException
592         *             if the background img cannot be read
593         */
594        private WPVSScene createScene( GetView request, ViewPoint viewPoint, List<ResolutionStripe> stripes,
595                                       Envelope sceneBBox )
596                                throws OGCWebServiceException {
597            if ( stripes == null || stripes.size() == 0 ) {
598                String msg = "No resolutionStripes were given, therefore no scene can be created.";
599                LOG.logError( msg );
600                throw new OGCWebServiceException( msg );
601            }
602            LOG.logDebug( "Creating scene with " + stripes.size() + " number of resolution stripes " );
603            OrderedGroup scene = new OrderedGroup();
604            int dgmType = ResolutionStripe.ELEVATION_MODEL_UNKNOWN;
605            for ( int i = 0; i < stripes.size() && dgmType == ResolutionStripe.ELEVATION_MODEL_UNKNOWN; ++i ) {
606                dgmType = stripes.get( i ).getDGMType();
607            }
608            if ( dgmType == ResolutionStripe.ELEVATION_MODEL_POINTS ) {
609                LOG.logDebug( "The elevation model uses points, therefore generating one terrain." );
610    
611                List<Point3d> terrainPoints = new ArrayList<Point3d>();
612                double sceneWidth = sceneBBox.getWidth();
613                double sceneHeight = sceneBBox.getHeight();
614                double sceneMinX = sceneBBox.getMin().getX();
615                double sceneMinY = sceneBBox.getMin().getY();
616    
617                /*
618                 * First find the resolution stripe with the best (==highest) resolution, and while doing so, add all
619                 * measurepoints to the point array.
620                 */
621                ResolutionStripe hasBestResolution = null;
622                for ( ResolutionStripe stripe : stripes ) {
623                    if ( stripe.getMeassurepointsAsList() != null ) {
624                        terrainPoints.addAll( stripe.getMeassurepointsAsList() );
625                    }
626                    if ( hasBestResolution == null || stripe.getMaxResolution() < hasBestResolution.getMaxResolution() ) {
627                        if ( stripe.getRequestHeightForBBox() != -1 && stripe.getRequestWidthForBBox() != -1 ) {
628                            hasBestResolution = stripe;
629                        }
630                    }
631    
632                }
633                // Allthough it will probably never happen, it might be better to check.
634                if ( hasBestResolution == null ) {
635                    LOG.logError( "No best resolutionStripe was found, this can happen if none of the stripes has a resolution or if the the requestwidths /heights of all returned -1 (e.g. were to large to be handled ) therefore no scene can be created." );
636                    throw new OGCWebServiceException( "Could not create scene due to internal errors",
637                                                      ExceptionCode.NOAPPLICABLECODE );
638                }
639    
640                BufferedImage texture = hasBestResolution.getResultTexture();
641                double stripeTextureWidth = hasBestResolution.getRequestWidthForBBox();
642                double stripeTextureHeight = hasBestResolution.getRequestHeightForBBox();
643                // if an error occurred the texture might be null, in which case we use the textureWidth
644                // and height.
645                if ( texture == null ) {
646                    LOG.logInfo( "The best ResolutionStripe has no texture (this normally means, an error occurred while invoking it's texture datasources) creating a new (empty) texture." );
647                    texture = new BufferedImage( (int) stripeTextureWidth, (int) stripeTextureHeight,
648                                                 BufferedImage.TYPE_INT_ARGB );
649                }
650    
651                String splittingMode = request.getVendorSpecificParameter( "SPLITTER" );
652                if ( "BBOX".equals( splittingMode ) ) {
653                    if ( stripes.size() != 1 ) {
654                        LOG.logError( "Allthough the bbox splitter is used, we have more then one ResolutionStripe, this may not be, using only the first Stripe" );
655                    }
656                    LOG.logDebug( "The request is a bbox, just using the first (and only) resolution stripe's texture." );
657                    sceneBBox = request.getBoundingBox();
658                } else {
659    
660                    /**
661                     * The goal is to create one large textures to which all other textures are upscaled and then painted
662                     * upon. To do so, first get the largest texture, which scale will be used to create an image (the
663                     * resulttexture) which has the the dimensions of the scenes bbox. All that has to be done then, is to
664                     * calculate for each the stripe, the relative position of it's bbox, to the scene's bbox and the scale
665                     * of it's texture compared to the result texture's dimensions. This only has one little drawback, if
666                     * the scene is large (a steep few for example) the texture can get larger as the available texture
667                     * width of the gpu, in this case another scaling has to be applied.
668                     */
669                    Envelope stripeBBox = hasBestResolution.getSurface().getEnvelope();
670    
671                    // calculate the relation between the scene and the stripes bbox. and calculate the
672                    // width and height of the resulting texture.
673                    double scaleW = sceneWidth / stripeBBox.getWidth();
674                    double scaleH = sceneHeight / stripeBBox.getHeight();
675                    double resultTextureWidth = stripeTextureWidth * scaleW;
676                    double resultTextureHeight = stripeTextureHeight * scaleH;
677    
678                    // Check the scale against the maximum texture size.
679                    double scale = getTextureScale( resultTextureWidth, resultTextureHeight );
680                    texture = new BufferedImage( (int) Math.floor( resultTextureWidth * scale ),
681                                                 (int) Math.floor( resultTextureHeight * scale ), texture.getType() );
682    
683                    resultTextureHeight = texture.getHeight();
684                    resultTextureWidth = texture.getWidth();
685    
686                    // Get the graphics object and set the hints to maximum quality.
687                    Graphics2D g2d = (Graphics2D) texture.getGraphics();
688                    /**
689                     * We tried to set the rendering hints to different values here, but it seems, the standard values
690                     * result in the quickest and best quality, because j3d will do antialiasing itself again.
691                     */
692    
693                    // the transform will scale and translate the requested textures onto the one master
694                    // texture.
695                    AffineTransform origTransform = g2d.getTransform();
696    
697                    long paintTime = System.currentTimeMillis();
698                    // now draw all available texture onto the result texture.
699    
700                    for ( ResolutionStripe stripe : stripes ) {
701                        BufferedImage stripeTexture = stripe.getResultTexture();
702    
703                        if ( stripeTexture != null ) {
704    
705                            /**
706                             * First calculate the position of this stripe's texture as if it was to be drawn upon it's own
707                             * all fitting texture.
708                             */
709                            stripeBBox = stripe.getSurface().getEnvelope();
710                            // find the offset of the stripes bbox to the scene bbox.
711                            double realDistX = ( stripeBBox.getMin().getX() - sceneMinX ) / sceneWidth;
712                            double realDistY = ( stripeBBox.getMin().getY() - sceneMinY ) / sceneHeight;
713    
714                            /**
715                             * calculate the scale of the stripes' bbox to the scene's bbox
716                             */
717                            scaleW = sceneWidth / stripeBBox.getWidth();
718                            scaleH = sceneHeight / stripeBBox.getHeight();
719                            /**
720                             * Calculate the virtual all-fitting-texture for this stripe
721                             */
722                            double tmpTextureWidth = stripeTexture.getWidth() * scaleW;
723                            double tmpTextureHeight = stripeTexture.getHeight() * scaleH;
724    
725                            /**
726                             * It might happen, that the viratula all-fitting-texture of the upscaled texture would have
727                             * been to large for the texture memory to fit, therefore this scaling factor has to be taken
728                             * into account as well.
729                             */
730                            double maxTextureScale = getTextureScale( resultTextureWidth, resultTextureHeight );
731    
732                            /**
733                             * Calculate the scale of the virtual all-fitting-texture [with width
734                             * =(tmpTextureWidth*maxTextureScale) and height= (tmpTextureHeight*maxTextureScale) ], to the
735                             * real result texture.
736                             */
737                            double scaleX = resultTextureWidth / ( tmpTextureWidth * maxTextureScale );
738                            double scaleY = resultTextureHeight / ( tmpTextureHeight * maxTextureScale );
739    
740                            /**
741                             * And calculate the position within the virtual all-fitting-texture.
742                             */
743                            double posX = tmpTextureWidth * realDistX;
744                            double posY = tmpTextureHeight
745                                          - ( ( tmpTextureHeight * realDistY ) + stripeTexture.getHeight() );
746                            if ( posY < 0 ) {
747                                posY = 0;
748                            }
749                            if ( posY > tmpTextureHeight ) {
750                                posY = (int) Math.floor( tmpTextureHeight );
751                            }
752                            if ( posX < 0 ) {
753                                posX = 0;
754                            }
755                            LOG.logDebug( "posX: " + posX );
756                            LOG.logDebug( "posY: " + posY );
757    
758                            /**
759                             * First translate then set the scale of the g2d and after the drawing reset the transformation.
760                             */
761                            g2d.translate( Math.round( posX * scaleX ), Math.round( posY * scaleY ) );
762                            g2d.scale( scaleX, scaleY );
763                            g2d.drawImage( stripeTexture, 0, 0, new Color( 0, 0, 0, 0 ), null );
764                            g2d.setTransform( origTransform );
765                        } else {
766                            LOG.logError( "One of the resolutionStripes has no texture, so nothing to draw" );
767                        }
768                    }
769    
770                    if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
771                        LOG.logDebug( "The actual drawing on the g2d with preferred took: "
772                                      + ( ( System.currentTimeMillis() - paintTime ) / 1000d ) + "seconds." );
773                        try {
774                            File f = File.createTempFile( "resultTexture", ".png" );
775                            f.deleteOnExit();
776                            ImageIO.write( texture, "png", f );
777                            LOG.logDebug( "Wrote texture: " + f.getAbsolutePath() );
778                        } catch ( IOException e ) {
779                            LOG.logDebug( "Could not write texture for debugging purposes because: ", e.getMessage() );
780                        }
781                    }
782                    g2d.dispose();
783                }
784                /**
785                 * Create a triangle terrain which will receive the texture.
786                 */
787                TriangleTerrain terrain = new TriangleTerrain( terrainPoints, sceneBBox,
788                                                               config.getDeegreeParams().getMinimalTerrainHeight(),
789                                                               request.getScale() );
790                terrain.setTexture( texture );
791                terrain.createTerrain();
792                scene.addChild( terrain );
793            }
794            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
795                if ( dgmType == ResolutionStripe.ELEVATION_MODEL_POINTS ) {
796                    LOG.logDebug( "The elevationmodel uses meassurepoints." );
797                } else if ( dgmType == ResolutionStripe.ELEVATION_MODEL_GRID ) {
798                    LOG.logDebug( "The elevationmodel uses a grid." );
799                } else if ( dgmType == ResolutionStripe.ELEVATION_MODEL_UNKNOWN ) {
800                    LOG.logDebug( "The elevationmodel uses an unknown format." );
801                }
802            }
803            for ( ResolutionStripe stripe : stripes ) {
804                // create one large triangled Shape3D object.
805                LOG.logDebug( "Getting Shape3D object from Stripe: " + stripe );
806                scene.addChild( stripe.getJava3DRepresentation() );
807            }
808    
809            Calendar date = TimeTools.createCalendar( request.getVendorSpecificParameters().get( "DATETIME" ) );
810            return new WPVSScene( scene, viewPoint, date, null, createBackground( viewPoint, request ) );
811        }
812    
813        /**
814         * Calculates the scale to fit the largest of the two given params to the maximum texture size.
815         * 
816         * @param width
817         *            the originalwidth of the texture
818         * @param height
819         *            the original height of the texture
820         * @return a scale to fit the width and height in the configured maximumtexture width. or 1 if both are smaller.
821         */
822        private double getTextureScale( double width, double height ) {
823            if ( width > WPVSConfiguration.texture2DMaxSize || height > WPVSConfiguration.texture2DMaxSize ) {
824                return WPVSConfiguration.texture2DMaxSize / ( ( width > height ) ? width : height );
825            }
826            return 1;
827        }
828    
829        /**
830         * Renders the scene and the resulting image.
831         * 
832         * @return a new image representing a screen shot of the scene
833         */
834        public BufferedImage renderScene() {
835    
836            BufferedImage image = renderer.renderScene();
837    
838            if ( !config.getDeegreeParams().isWatermarked() ) {
839                paintCopyright( image );
840            }
841    
842            return image;
843    
844        }
845    
846        /**
847         * prints a copyright note at left side of the map bottom. The copyright note will be extracted from the WMS
848         * capabilities/configuration
849         * 
850         * @param image
851         *            the image onto which to print the copyright message/image
852         */
853        private void paintCopyright( BufferedImage image ) {
854    
855            Graphics2D g2 = (Graphics2D) image.getGraphics();
856    
857            WPVSDeegreeParams dp = config.getDeegreeParams();
858            String copyright = dp.getCopyright();
859            if ( config.getDeegreeParams().getCopyrightImage() != null ) {
860                g2.drawImage( config.getDeegreeParams().getCopyrightImage(), 0,
861                              image.getHeight() - config.getDeegreeParams().getCopyrightImage().getHeight(), null );
862            } else if ( copyright != null && !"".equals( copyright ) ) {
863    
864                g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
865                g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
866    
867                final int fontSize = 14;
868                final int margin = 5;
869    
870                int imgHeight = image.getHeight();
871    
872                Font f = new Font( "SANSSERIF", Font.PLAIN, fontSize );
873                g2.setFont( f );
874                // draw text shadow
875                g2.setColor( Color.black );
876                g2.drawString( copyright, margin, imgHeight - margin );
877                // draw text
878                g2.setColor( Color.white );
879                g2.drawString( copyright, margin - 1, imgHeight - margin - 1 );
880    
881            }
882            g2.dispose();
883        }
884    
885        /**
886         * Checks if the image size is compatible with that given in the configuration
887         * 
888         * @throws OGCWebServiceException
889         */
890        private void validateImageSize( GetView request )
891                                throws OGCWebServiceException {
892            int width = request.getImageDimension().width;
893            int maxWidth = config.getDeegreeParams().getMaxViewWidth();
894            if ( width > maxWidth ) {
895                throw new OGCWebServiceException(
896                                                  StringTools.concat(
897                                                                      100,
898                                                                      "Requested view width exceeds allowed maximum width of ",
899                                                                      new Integer( maxWidth ), " pixels." ) );
900            }
901            int height = request.getImageDimension().height;
902            int maxHeight = config.getDeegreeParams().getMaxViewHeight();
903            if ( height > maxHeight ) {
904                throw new OGCWebServiceException(
905                                                  StringTools.concat(
906                                                                      100,
907                                                                      "Requested view height exceeds allowed maximum height of ",
908                                                                      new Integer( maxHeight ), " pixels." ) );
909            }
910    
911        }
912    
913        /**
914         * @return Returns the generated scene for this request... handy for debugging.
915         */
916        public WPVSScene getTheScene() {
917            return theScene;
918        }
919    
920        /**
921         * @return the scene renderer.
922         */
923        public OffScreenWPVSRenderer getRenderer() {
924            return renderer;
925        }
926    
927    }