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