001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }