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