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