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