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 }