001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wpvs/DefaultGetViewHandler.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/exse/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     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.image.BufferedImage;
052    import java.io.IOException;
053    import java.net.URL;
054    import java.util.ArrayList;
055    import java.util.Calendar;
056    import java.util.HashMap;
057    import java.util.List;
058    import java.util.Set;
059    import java.util.SortedMap;
060    import java.util.TreeMap;
061    import java.util.concurrent.Callable;
062    import java.util.concurrent.CancellationException;
063    
064    import javax.imageio.ImageIO;
065    import javax.media.j3d.Background;
066    import javax.media.j3d.BoundingSphere;
067    import javax.media.j3d.BranchGroup;
068    import javax.media.j3d.ImageComponent2D;
069    import javax.media.j3d.Node;
070    import javax.vecmath.Color3f;
071    import javax.vecmath.Point3d;
072    
073    import org.deegree.framework.concurrent.ExecutionFinishedEvent;
074    import org.deegree.framework.concurrent.Executor;
075    import org.deegree.framework.log.ILogger;
076    import org.deegree.framework.log.LoggerFactory;
077    import org.deegree.framework.util.StringTools;
078    import org.deegree.framework.util.TimeTools;
079    import org.deegree.i18n.Messages;
080    import org.deegree.model.crs.CRSException;
081    import org.deegree.model.crs.CRSFactory;
082    import org.deegree.model.crs.CRSTransformationException;
083    import org.deegree.model.crs.CoordinateSystem;
084    import org.deegree.model.crs.GeoTransformer;
085    import org.deegree.model.crs.IGeoTransformer;
086    import org.deegree.model.spatialschema.Envelope;
087    import org.deegree.model.spatialschema.Position;
088    import org.deegree.model.spatialschema.Surface;
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 {@link GetView} request lands
110     * the configured Datasources are gathered and the scene is put together.
111     * 
112     * @author <a href="mailto:taddei@lat-lon.de">Ugo Taddei</a>
113     * @author last edited by: $Author: rbezema $
114     * 
115     * $Revision: 7965 $, $Date: 2007-08-09 12:03:55 +0200 (Do, 09 Aug 2007) $
116     * 
117     */
118    public class DefaultGetViewHandler extends GetViewHandler {
119    
120        static private final ILogger LOG = LoggerFactory.getLogger( DefaultGetViewHandler.class );
121    
122        private WPVSConfiguration config;
123    
124        private URL backgroundImgURL;
125    
126        private WPVSScene theScene;
127    
128        private OffScreenWPVSRenderer renderer;
129    
130        /**
131         * Constructor for DefaultGetViewHandler.
132         * 
133         * @param owner
134         *            the service creating this handler
135         */
136        public DefaultGetViewHandler( WPVService owner ) {
137            super( owner );
138    
139            this.config = owner.getConfiguration();
140        }
141    
142        /**
143         * This Method handles a clients GetView request by creating the appropriate (configured) Datasources, the
144         * {@link ResolutionStripe}s, the requeststripes (which are actually axisalligned Resolutionsripes) and finally
145         * putting them all together in a java3d scene. The creation of the Shape3D Objects (by requesting the
146         * ResolutionStripe relevant Datasources) is done in separate Threads for each ResolutionStripe (which is in
147         * conflict with the deegree styleguides) using the {@link Executor} class.
148         * 
149         * @see org.deegree.ogcwebservices.wpvs.GetViewHandler#handleRequest(org.deegree.ogcwebservices.wpvs.operation.GetView)
150         */
151        @Override
152        public GetViewResponse handleRequest( final GetView request )
153                                throws OGCWebServiceException {
154    
155            // request = req;
156    
157            validateImageSize( request );
158    
159            List<Dataset> validDatasets = new ArrayList<Dataset>();
160            String errorMessage = getValidRequestDatasets( request, validDatasets );
161            if ( validDatasets.size() == 0 ) {
162                throw new OGCWebServiceException(
163                                                  StringTools.concat(
164                                                                      errorMessage.length() + 100,
165                                                                      "Your request yields no results for given reasons:\n",
166                                                                      errorMessage ) );
167    
168            }
169    
170            ElevationModel elevationModel = getValidElevationModel( request.getElevationModel() );
171            LOG.logDebug( "Requested elevationModel: " + elevationModel );
172    
173            if ( request.getFarClippingPlane() > config.getDeegreeParams().getMaximumFarClippingPlane() ) {
174                request.setFarClippingPlane( config.getDeegreeParams().getMaximumFarClippingPlane() );
175            }
176    
177            ViewPoint viewPoint = new ViewPoint( request, getTerrainHeightAboveSeaLevel( null ) );
178            LOG.logDebug( "Viewpoint: " + viewPoint );
179            
180            // viewPoint.setTerrainDistanceToSeaLevel( getTerrainHeightAboveSeaLevel(
181            // viewPoint.getObserverPosition() ) );
182    
183            ArrayList<ResolutionStripe> resolutionStripes = createRequestBoxes( request, viewPoint,
184                                                                                config.getSmallestMinimalScaleDenomiator() );
185            LOG.logDebug( "Found number of resolutionStripes: " + resolutionStripes.size() );
186    
187            if ( resolutionStripes.size() == 0 ) {
188                throw new OGCWebServiceException(
189                                                  StringTools.concat( 200,
190                                                                      "There were no RequestBoxes found, therefor this WPVS-request is invalid" ) );
191    
192            }
193    
194            Surface visibleArea = viewPoint.getVisibleArea();
195            
196            LOG.logDebug( "Visible Area: " + visibleArea );
197    
198            findValidDataSourcesFromDatasets( validDatasets, resolutionStripes, request.getOutputFormat(), visibleArea );
199            LOG.logDebug( "Using validDatasets: " + validDatasets );
200            
201            findValidEMDataSourceFromElevationModel( elevationModel, resolutionStripes, visibleArea );
202            LOG.logDebug( "Using elevationModel: " + elevationModel );
203    
204            // will also check is background is valid, before doing any hard work
205            this.backgroundImgURL = createBackgroundImageURL( request );
206    
207    
208            LOG.logDebug( "Backgroundurl : " + backgroundImgURL );
209    
210            Executor exec = Executor.getInstance();
211            List<ExecutionFinishedEvent<ResolutionStripe>> resultingBoxes = null;
212            try {
213                resultingBoxes = exec.performSynchronously( new ArrayList<Callable<ResolutionStripe>>( resolutionStripes ),
214                                                            config.getDeegreeParams().getRequestTimeLimit() );
215            } catch ( InterruptedException ie ) {
216                throw new OGCWebServiceException(
217                                                  StringTools.concat( 200,
218                                                                      "A Threading-Error occurred while placing your request." ) );
219    
220            }
221            
222            LOG.logDebug( "All resolutionstripes finished executing." );
223    
224            if ( resultingBoxes != null && resultingBoxes.size() > 0 ) {
225                // check if some of the resolutionstripes finished execution before the configured max
226                // life time.
227                int k = 0;
228                for ( ExecutionFinishedEvent<ResolutionStripe> efe : resultingBoxes ) {
229                    try {
230                        efe.getResult();
231                    } catch ( CancellationException ce ) {
232                        k++;
233                    } catch ( Throwable e ) {
234                        // not interested in something else
235                    }
236                }
237                if ( k == resultingBoxes.size() ) {
238                    throw new OGCWebServiceException(
239                                                      Messages.getMessage(
240                                                                           "WPVS_EXCEEDED_REQUEST_TIME",
241                                                                           new Double(
242                                                                                       config.getDeegreeParams().getRequestTimeLimit() * 0.001 ) ).toString() );
243                }
244                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
245                    LOG.logDebug( "Outputting textures for all finished resolutionsSripes." );
246                    for ( ResolutionStripe stripe : resolutionStripes ) {
247                        stripe.outputTextures();
248                    }
249                }
250                theScene = createScene( request, viewPoint, resolutionStripes );
251    
252                renderer = new OffScreenWPVSRenderer( config.getDeegreeParams().getNearClippingPlane(), theScene, request.getImageDimension().width,
253                                                      request.getImageDimension().height );
254                LOG.logDebug( "Trying to render Scene." );
255                BufferedImage output = renderScene();
256                LOG.logDebug( "Returning rendered Scene." );
257                return new GetViewResponse( output, request.getOutputFormat() );
258            }
259            return null;
260        }
261    
262        /**
263         * @param elevationModelName
264         * @return an elevationModell or <code>null</code> if no name was given
265         * @throws OGCWebServiceException
266         *             if no elevationModel is found for the given elevationmodelname
267         */
268        private ElevationModel getValidElevationModel( String elevationModelName )
269                                throws OGCWebServiceException {
270            ElevationModel resultEMModel = null;
271            if ( elevationModelName != null ) {
272                resultEMModel = config.findElevationModel( elevationModelName );// dataset.getElevationModel();
273                if ( resultEMModel == null ) {
274                    throw new OGCWebServiceException( StringTools.concat( 150, "ElevationModel '", elevationModelName,
275                                                                          "' is not known to the WPVS" ) );
276                }
277            }
278            return resultEMModel;
279        }
280    
281        /**
282         * Finds the datasets which can handle the requested crs's, and check if they are defined inside the requested bbox
283         * 
284         * @param request
285         *            the GetView request
286         * @param coordSys
287         *            the name of the requested coordsys
288         * @return an ArrayList containing all the datasets which comply with the requested crs.
289         * @throws OGCWebServiceException
290         */
291        private String getValidRequestDatasets( GetView request, List<Dataset> resultDatasets )
292                                throws OGCWebServiceException {
293            // ArrayList<Dataset> resultDatasets = new ArrayList<Dataset>();
294            Envelope bbox = request.getBoundingBox();
295            List<String> datasets = request.getDatasets();
296            CoordinateSystem coordSys = request.getCrs();
297    
298            // If the BoundingBox request not is
299            try {
300                if ( !"EPSG:4326".equalsIgnoreCase( coordSys.getFormattedString() ) ) {
301                    // transform the bounding box of the request to EPSG:4326/WGS 84
302                    IGeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
303                    bbox = gt.transform( bbox, coordSys );
304                }
305            } catch ( CRSTransformationException e ) {
306                LOG.logError( e.getMessage(), e );
307                throw new OGCWebServiceException( e.getMessage() );
308            } catch ( CRSException e ) {
309                LOG.logError( e.getMessage(), e );
310                throw new OGCWebServiceException( e.getMessage() );
311            }
312            StringBuffer errorMessage = new StringBuffer( 1000 );
313            for ( String dset : datasets ) {
314                Dataset configuredDataset = config.findDataset( dset );
315                if ( configuredDataset != null ) {
316                    CoordinateSystem[] dataSetCRS = configuredDataset.getCrs();
317                    boolean isCoordSysSupported = false;
318                    boolean requestIntersectsWithDataset = false;
319                    for ( CoordinateSystem crs : dataSetCRS ) {
320                        if ( crs.equals( coordSys ) ) {
321                            isCoordSysSupported = true;
322                            // lookslike compatible crs therefor check if bbox intersect
323                            if ( configuredDataset.getWgs84BoundingBox().intersects( bbox ) ) {
324                                requestIntersectsWithDataset = true;
325                                if ( !resultDatasets.contains( configuredDataset ) )
326                                    resultDatasets.add( configuredDataset );
327                            }
328                        }
329                    }
330                    if ( !isCoordSysSupported ) {
331                        errorMessage.append( "Requested Dataset -" + dset + "- does not support requested crs: " + coordSys
332                                             + ".\n" );
333                    } else if ( !requestIntersectsWithDataset ) {
334                        errorMessage.append( "Requested Dataset -" + dset
335                                             + "- does not intersect with the requested bbox.\n" );
336                    }
337                } else {
338                    throw new InconsistentRequestException( Messages.getMessage( "WPVS_GETVIEW_INVALID_DATASET", dset ) );
339                }
340            }
341    
342            return errorMessage.toString();
343        }
344    
345        /**
346         * Finds the valid datasources in the configured (and allready checked) datasets.
347         * 
348         * @param datasets
349         *            the datasets which are valid for this request
350         */
351        private void findValidDataSourcesFromDatasets( List<Dataset> datasets, ArrayList<ResolutionStripe> stripes,
352                                                       String outputFormat, Surface visibleArea ) {
353            double resolution = 0;
354            for ( ResolutionStripe stripe : stripes ) {
355                stripe.setOutputFormat( outputFormat );
356                resolution = stripe.getMaxResolutionAsScaleDenominator();
357                for ( int i = 0; i < datasets.size(); ++i ) {
358                    Dataset dset = datasets.get( i );
359                    AbstractDataSource[] dataSources = dset.getDataSources();
360    
361                    for ( AbstractDataSource ads : dataSources ) {
362                        // System.out.println( "AbstractDataSource: " + ads );
363                        if ( resolution >= dset.getMinimumScaleDenominator()
364                             && resolution < dset.getMaximumScaleDenominator()
365                             && resolution >= ads.getMinScaleDenominator() && resolution < ads.getMaxScaleDenominator()
366                             && ( ( ads.getValidArea() != null ) ? ads.getValidArea().intersects( visibleArea ) : true ) ) {
367                            if ( ads.getServiceType() == AbstractDataSource.LOCAL_WFS
368                                 || ads.getServiceType() == AbstractDataSource.REMOTE_WFS ) {
369                                stripe.addFeatureCollectionDataSource( ads );
370                            } else if ( ads.getServiceType() == AbstractDataSource.LOCAL_WMS
371                                        || ads.getServiceType() == AbstractDataSource.REMOTE_WMS
372                                        || ads.getServiceType() == AbstractDataSource.LOCAL_WCS
373                                        || ads.getServiceType() == AbstractDataSource.REMOTE_WCS ) {
374                                stripe.addTextureDataSource( ads );
375                            }
376                        }
377                    }
378                }
379            }
380        }
381    
382        /**
383         * @param elevationModel
384         *            and it's datasources.
385         */
386        private void findValidEMDataSourceFromElevationModel( ElevationModel elevationModel,
387                                                              ArrayList<ResolutionStripe> stripes, Surface visibleArea ) {
388            if ( elevationModel != null ) {
389                AbstractDataSource[] emDataSources = elevationModel.getDataSources();
390                Dataset dataset = elevationModel.getParentDataset();
391                for ( ResolutionStripe stripe : stripes ) {
392                    double resolution = stripe.getMaxResolution();
393                    for ( AbstractDataSource ads : emDataSources ) {
394                        if ( resolution >= dataset.getMinimumScaleDenominator()
395                             && resolution < dataset.getMaximumScaleDenominator()
396                             && resolution >= ads.getMinScaleDenominator() && resolution < ads.getMaxScaleDenominator()
397                             && ( ( ads.getValidArea() != null ) ? ads.getValidArea().intersects( visibleArea ) : true ) ) {
398                            stripe.setElevationModelDataSource( ads );
399                        }
400                    }
401                }
402            }
403        }
404    
405        /**
406         * Extracts from the request and the configuration the URL behind teh name of a given BACKGROUND
407         * 
408         * @return the URL, under which the background image is found
409         * @throws OGCWebServiceException
410         *             if no URL with the name given by 'BACKGROUND' can be found.
411         */
412        private URL createBackgroundImageURL( GetView request )
413                                throws OGCWebServiceException {
414    
415            String imageName = request.getVendorSpecificParameter( "BACKGROUND" );
416            URL imgURL = null;
417            if ( imageName != null ) {
418                imgURL = (URL) config.getDeegreeParams().getBackgroundMap().get( imageName );
419                if ( imgURL == null ) {
420                    String s = StringTools.concat( 100, "Cannot find any image referenced", "by parameter BACKGROUND=",
421                                                   imageName );
422                    throw new OGCWebServiceException( s );
423                }
424            }
425    
426            return imgURL;
427        }
428    
429        /**
430         * Creates a Java3D Node representing the background.
431         * 
432         * @param minAltitude
433         *            the minimum altitude of the backgroudn plane
434         * @param viewPoint
435         *            the viewersposition
436         * @return a new Node containing a geometry representing the background
437         * @throws IOException
438         */
439        private Node createBackground( ViewPoint viewPoint, GetView request )
440                                throws OGCWebServiceException {
441    
442            Point3d observer = viewPoint.getObserverPosition();
443    
444            Background bg = new Background( new Color3f( request.getBackgroundColor() ) );
445            BoundingSphere bounds = new BoundingSphere( observer,
446                                                        config.getDeegreeParams().getMaximumFarClippingPlane() * 1.5 );
447    
448            bg.setApplicationBounds( bounds );
449            try {
450                if ( backgroundImgURL != null ) {
451                    BufferedImage buffImg = ImageIO.read( backgroundImgURL );
452    
453                    // scale image to fill the whole bakground
454                    BufferedImage tmpImg = new BufferedImage( request.getImageDimension().width,
455                                                              request.getImageDimension().height, buffImg.getType() );
456                    Graphics g = tmpImg.getGraphics();
457                    g.drawImage( buffImg, 0, 0, tmpImg.getWidth() - 1, tmpImg.getHeight() - 1, null );
458                    g.dispose();
459    
460                    ImageComponent2D img = new TextureLoader( tmpImg ).getImage();
461                    bg.setImage( img );
462                }
463            } catch ( IOException e ) {
464                LOG.logError( e.getMessage(), e );
465                String s = StringTools.concat( 100, "Could not create backgound image: ", e.getMessage() );
466                throw new OGCWebServiceException( s );
467            }
468    
469            return bg;
470        }
471    
472        /**
473         * Creates the request boxes from the parameters available i teh incoming request.
474         * 
475         * @param viewPoint
476         *            where the viewer is
477         * @return a new array of surfaces representing the area in which data will be collected
478         */
479        private ArrayList<ResolutionStripe> createRequestBoxes( GetView request, ViewPoint viewPoint,
480                                                                double smallestMinimalScaleDenominator ) {
481            ArrayList<ResolutionStripe> requestStripes = new ArrayList<ResolutionStripe>();
482            String splittingMode = request.getVendorSpecificParameter( "SPLITTER" );
483            StripeFactory stripesFactory = new StripeFactory( viewPoint, smallestMinimalScaleDenominator );
484            int imageWidth = request.getImageDimension().width;
485            if ( "BBOX".equals( splittingMode ) ) {
486                requestStripes = stripesFactory.createBBoxResolutionStripe(
487                                                                            request.getBoundingBox(),
488                                                                            imageWidth,
489                                                                            getTerrainHeightAboveSeaLevel( viewPoint.getPointOfInterest() ),
490                                                                            request.getScale() );
491            } else {
492                // Calculate the resolution stripes perpendicular to the viewdirection
493                requestStripes = stripesFactory.createResolutionStripes(
494                                                                         request.getImageDimension().width,
495                                                                         getTerrainHeightAboveSeaLevel( viewPoint.getPointOfInterest() ),
496                                                                         null, request.getScale() );
497                QuadTreeSplitter splittree = new QuadTreeSplitter( requestStripes, request.getImageDimension().width,
498                                                                   config.getDeegreeParams().isRequestQualityPreferred(),
499                                                                   null );
500                requestStripes = splittree.getRequestQuads( null, config.getDeegreeParams().getExtendRequestPercentage() );
501            }
502            return requestStripes;
503        }
504    
505        /**
506         * Should return the height of the terrain above the sealevel. TODO just returns a constant value, i.e. the
507         * configured MinimalTerrainHeight.
508         * 
509         * @param eyePositionX
510         *            to find the height of the sealevel for (not used yet)
511         * @param eyePositionY
512         *            to find the height of the sealevel for (not used yet)
513         * @param eyePositionZ
514         *            to find the height of the sealevel for (not used yet)
515         * @return the height above the seaLevel.
516         */
517        protected double getTerrainHeightAboveSeaLevel( double eyePositionX, double eyePositionY, double eyePositionZ ) {
518            return getTerrainHeightAboveSeaLevel( new Point3d( eyePositionX, eyePositionY, eyePositionZ ) );
519        }
520    
521        /**
522         * Should return the height of the terrain above the sealevel. TODO just returns a constant value, i.e. the
523         * configured MinimalTerrainHeight.
524         * 
525         * @param eyePosition
526         *            to find the height of the sealevel for (not used yet)
527         * 
528         * @return the height above the seaLevel.
529         */
530        protected double getTerrainHeightAboveSeaLevel( @SuppressWarnings("unused")
531        Point3d eyePosition ) {
532            return config.getDeegreeParams().getMinimalTerrainHeight();
533        }
534    
535        /**
536         * Creates a WPVS scene
537         * 
538         * @param viewPoint
539         *            position of the viewer
540         * @return a new scene
541         * @throws OGCWebServiceException
542         *             if the background img cannot be read
543         */
544        private WPVSScene createScene( GetView request, ViewPoint viewPoint, List<ResolutionStripe> stripes )
545                                throws OGCWebServiceException {
546    
547            if ( stripes == null && stripes.size() == 0 ) {
548                return null;
549            }
550            LOG.logDebug( "Creating scene with " + stripes.size() + " number of resolution stripes " );
551            BranchGroup scene = new BranchGroup();
552    
553            if ( stripes.get( 0 ).isDGMFromMeassurePoints() ) {
554                LOG.logDebug( "The elevation model uses points, therefore trying to 'sow' the patches to eachother." );
555                // first create an overall texture which will contain all textures
556                // double minX = Double.MAX_VALUE;
557                // double minY = Double.MAX_VALUE;
558                // double maxX = Double.MIN_VALUE;
559                // double maxY = Double.MIN_VALUE;
560    
561                // sort the resolutionstripe according to their y and x values of the minimal Point of
562                // their boundingboxes.
563                TreeMap<Double, SortedMap<Double, TriangleTerrain>> sortedStripesMinY = new TreeMap<Double, SortedMap<Double, TriangleTerrain>>();
564                TreeMap<Double, SortedMap<Double, TriangleTerrain>> sortedStripesMinX = new TreeMap<Double, SortedMap<Double, TriangleTerrain>>();
565    
566                // List<Point> measurePoints = new ArrayList<Point>();
567                for ( ResolutionStripe stripe : stripes ) {
568                    Position tmpMin = stripe.getSurface().getEnvelope().getMin();
569                    Double minXValue = new Double( tmpMin.getX() );
570                    Double minYValue = new Double( tmpMin.getY() );
571                    // if ( textureWidth < stripe.getRequestWidthForBBox() ) {
572                    // textureWidth = stripe.getRequestWidthForBBox();
573                    // }
574                    // measurePoints.addAll( stripe.getMeasurePoints() );
575    
576                    // Position tmpMax = stripe.getSurface().getEnvelope().getMax();
577                    // Double maxXValue = new Double( tmpMax.getX() );
578                    // Double maxYValue = new Double( tmpMax.getY() );
579                    //
580                    // // defining epsilon area around the key
581                    Double fromMinYSmall = new Double( tmpMin.getY() - 0.001 );
582                    Double fromMinYLarge = new Double( tmpMin.getY() + 0.001 );
583                    Double fromMinXSmall = new Double( tmpMin.getX() - 0.001 );
584                    Double fromMinXLarge = new Double( tmpMin.getX() + 0.001 );
585    
586                    // finding keys which are epsilon near to the min and max values
587                    SortedMap<Double, SortedMap<Double, TriangleTerrain>> tileMinYMap = sortedStripesMinY.subMap(
588                                                                                                                  fromMinYSmall,
589                                                                                                                  fromMinYLarge );
590                    SortedMap<Double, SortedMap<Double, TriangleTerrain>> tileMinXMap = sortedStripesMinX.subMap(
591                                                                                                                  fromMinXSmall,
592                                                                                                                  fromMinXLarge );
593                    // sort to y and then to x
594    
595                    if ( !tileMinYMap.isEmpty() ) {
596                        SortedMap<Double, TriangleTerrain> stripeTree = tileMinYMap.get( tileMinYMap.firstKey() );
597                        stripeTree.put( minXValue, (TriangleTerrain) stripe.getElevationModel() );
598                    } else {// no matching min_Y_key found, create new Tree for this min_X_Value
599                        SortedMap<Double, TriangleTerrain> stripeTree = new TreeMap<Double, TriangleTerrain>();
600                        stripeTree.put( minXValue, (TriangleTerrain) stripe.getElevationModel() );
601                        sortedStripesMinY.put( minYValue, stripeTree );
602                    }
603                    // sort to x and than to y
604                    if ( !tileMinXMap.isEmpty() ) {
605                        SortedMap<Double, TriangleTerrain> stripeTree = tileMinXMap.get( tileMinXMap.firstKey() );
606                        stripeTree.put( minYValue, (TriangleTerrain) stripe.getElevationModel() );
607                    } else {// no matching min_X_key found, create new Tree for this min_X_Value
608                        SortedMap<Double, TriangleTerrain> stripeTree = new TreeMap<Double, TriangleTerrain>();
609                        stripeTree.put( minYValue, (TriangleTerrain) stripe.getElevationModel() );
610                        sortedStripesMinX.put( minXValue, stripeTree );
611                    }
612    
613                    // if ( tmpMin.getX() < minX ) {
614                    // minX = tmpMin.getX();
615                    // }
616                    // if ( tmpMin.getY() < minY ) {
617                    // minY = tmpMin.getY();
618                    // }
619                    // if ( tmpMax.getX() > maxX ) {
620                    // maxX = tmpMax.getX();
621                    // }
622                    // if ( tmpMax.getY() > maxY ) {
623                    // maxY = tmpMax.getY();
624                    // }
625                }
626    
627                createSeamsWithScanline( sortedStripesMinY, true );
628                createSeamsWithScanline( sortedStripesMinX, false );
629            }
630    
631            // dgm is a heightmap
632            for ( ResolutionStripe stripe : stripes ) {
633                // create one large triangled Shape3D object.
634                LOG.logDebug( "Getting Shape3D object for Stripe: " + stripe );
635                scene.addChild( stripe.getJava3DRepresentation() );
636            }
637            // TriangleTerrain tt = new TriangleTerrain();
638            Calendar date = TimeTools.createCalendar( request.getVendorSpecificParameters().get( "DATETIME" ) );
639            return new WPVSScene( scene, viewPoint, date, null, createBackground( viewPoint, request ) );
640    
641        }
642    
643        private void createSeamsWithScanline( SortedMap<Double, SortedMap<Double, TriangleTerrain>> sortedTerrains,
644                                              boolean ySorted ) {
645            Set<Double> firstCoordinates = sortedTerrains.keySet();
646            SortedMap<Double, TriangleTerrain> currentScanline = new TreeMap<Double, TriangleTerrain>();
647            // ArrayList<Double> removeFromScanline = new ArrayList<Double>( 10 );
648    
649            HashMap<TriangleTerrain, ArrayList<TriangleTerrain>> visitedPairs = new HashMap<TriangleTerrain, ArrayList<TriangleTerrain>>();
650    
651            for ( Double firstCoordinate : firstCoordinates ) {
652                // System.out.println( "\n*******\nnew firstkey\n*********\n" );
653                SortedMap<Double, TriangleTerrain> resMap = sortedTerrains.get( firstCoordinate );
654                if ( resMap != null ) {
655                    Set<Double> secondCoordinates = resMap.keySet();
656                    for ( Double secondCoordinate : secondCoordinates ) {
657                        TriangleTerrain terrain = resMap.get( secondCoordinate );
658                        if ( terrain != null ) {
659                            // System.out.println( "found a nother stripe" );
660                            currentScanline.put( secondCoordinate, terrain );
661                        }
662                    }
663                }
664                Set<Double> keySet = currentScanline.keySet();
665                if ( keySet != null && keySet.size() > 0 ) {
666                    Double[] scanlineKeys = new Double[keySet.size()];
667                    keySet.toArray( scanlineKeys );
668                    // remove all passed stripes from the current scanline set
669                    for ( int i = 0; i < scanlineKeys.length; ++i ) {
670                        TriangleTerrain terrain = currentScanline.get( scanlineKeys[i] );
671                        double comparator = ( ySorted ) ? terrain.getBoundingBox().getMax().getY()
672                                                       : terrain.getBoundingBox().getMax().getX();
673                        if ( firstCoordinate.doubleValue() >= comparator ) {
674                            // remove from visited too
675                            ArrayList<TriangleTerrain> allNeighbours = visitedPairs.get( terrain );
676                            if ( allNeighbours != null ) {
677                                allNeighbours.clear();
678                                visitedPairs.remove( terrain );
679                            }
680                            currentScanline.remove( scanlineKeys[i] );
681                        }
682                    }
683                    // iterate over all resolutionstripes currently "touched" by the scanline.
684                    if ( keySet.size() > 0 ) {
685                        scanlineKeys = new Double[keySet.size()];
686                        keySet.toArray( scanlineKeys );
687                        for ( int i = 0; i < scanlineKeys.length; ++i ) {
688                            TriangleTerrain terrain = currentScanline.get( scanlineKeys[i] );
689    
690                            if ( ( i + 1 ) < scanlineKeys.length ) {
691                                TriangleTerrain neighbourTerrain = currentScanline.get( scanlineKeys[i + 1] );
692                                ArrayList<TriangleTerrain> visitedNeighbours = visitedPairs.get( terrain );
693                                boolean visited = false;
694                                if ( visitedNeighbours != null ) {
695                                    visited = visitedNeighbours.contains( neighbourTerrain );
696                                }
697                                // two TriangleTerrains in the scanline have never been neighbours,
698                                // therefore a seam must be created according to the sorting order
699                                if ( !visited ) {
700                                    if ( ySorted ) {
701                                        terrain.createVerticalSeam( neighbourTerrain );
702                                    } else {
703                                        terrain.createHorizontalSeam( neighbourTerrain );
704                                    }
705                                    // and mark as neighbours.
706                                    if ( visitedNeighbours != null ) {
707                                        visitedNeighbours.add( neighbourTerrain );
708                                    } else {
709                                        visitedNeighbours = new ArrayList<TriangleTerrain>( 5 );
710                                        visitedNeighbours.add( neighbourTerrain );
711                                        visitedPairs.put( terrain, visitedNeighbours );
712                                    }
713                                }
714                            }
715                        }
716                    }
717                }
718            }
719        }
720    
721        /**
722         * Renders the scene and the resulting image.
723         * 
724         * @return a new image representing a screen shot of the scene
725         */
726        public BufferedImage renderScene() {
727    
728            BufferedImage image = renderer.renderScene();
729    
730            if ( !config.getDeegreeParams().isWatermarked() ) {
731                paintCopyright( image );
732            }
733    
734            return image;
735    
736        }
737    
738        /**
739         * prints a copyright note at left side of the map bottom. The copyright note will be extracted from the WMS
740         * capabilities/configuration
741         * 
742         * @param image
743         *            the image onto which to print the copyright message/image
744         */
745        private void paintCopyright( BufferedImage image ) {
746    
747            Graphics2D g2 = (Graphics2D) image.getGraphics();
748    
749            WPVSDeegreeParams dp = config.getDeegreeParams();
750            String copyright = dp.getCopyright();
751            if ( config.getDeegreeParams().getCopyrightImage() != null ) {
752                g2.drawImage( config.getDeegreeParams().getCopyrightImage(), 0,
753                              image.getHeight() - config.getDeegreeParams().getCopyrightImage().getHeight(), null );
754            } else if ( copyright != null ) {
755    
756                g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
757                g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
758    
759                final int fontSize = 14;
760                final int margin = 5;
761    
762                int imgHeight = image.getHeight();
763    
764                Font f = new Font( "SANSSERIF", Font.PLAIN, fontSize );
765                g2.setFont( f );
766                // draw text shadow
767                g2.setColor( Color.black );
768                g2.drawString( copyright, margin, imgHeight - margin );
769                // draw text
770                g2.setColor( Color.white );
771                g2.drawString( copyright, margin - 1, imgHeight - margin - 1 );
772    
773            }
774            g2.dispose();
775        }
776    
777        /**
778         * Checks if the image size is compatible with that given in the configuration
779         * 
780         * @throws OGCWebServiceException
781         */
782        private void validateImageSize( GetView request )
783                                throws OGCWebServiceException {
784            int width = request.getImageDimension().width;
785            int maxWidth = config.getDeegreeParams().getMaxViewWidth();
786            if ( width > maxWidth ) {
787                throw new OGCWebServiceException(
788                                                  StringTools.concat(
789                                                                      100,
790                                                                      "Requested view width exceeds allowed maximum width of ",
791                                                                      new Integer( maxWidth ), " pixels." ) );
792            }
793            int height = request.getImageDimension().height;
794            int maxHeight = config.getDeegreeParams().getMaxViewHeight();
795            if ( height > maxHeight ) {
796                throw new OGCWebServiceException(
797                                                  StringTools.concat(
798                                                                      100,
799                                                                      "Requested view height exceeds allowed maximum height of ",
800                                                                      new Integer( maxHeight ), " pixels." ) );
801            }
802    
803        }
804    
805        /**
806         * @return Returns the generated scene for this request... handy for debugging.
807         */
808        public WPVSScene getTheScene() {
809            return theScene;
810        }
811    
812        /**
813         * @return the scene renderer.
814         */
815        public OffScreenWPVSRenderer getRenderer() {
816            return renderer;
817        }
818    
819    }